diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js new file mode 100644 index 000000000..ef235c18d --- /dev/null +++ b/src/controllers/controller.bar.js @@ -0,0 +1,431 @@ +(function() { + + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.controllers.bar = function(chart, datasetIndex) { + this.initialize.call(this, chart, datasetIndex); + }; + + helpers.extend(Chart.controllers.bar.prototype, { + + initialize: function(chart, datasetIndex) { + this.chart = chart; + this.index = datasetIndex; + this.linkScales(); + this.addElements(); + }, + + linkScales: function() { + if (!this.getDataset().xAxisID) { + this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; + } + + if (!this.getDataset().yAxisID) { + this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + addElements: function() { + this.getDataset().metaData = this.getDataset().metaData || []; + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + }); + }, this); + }, + + reset: function() { + this.update(true); + }, + + update: function(reset) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + helpers.each(this.getDataset().metaData, function(rectangle, index) { + + var yScalePoint; + + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(rectangle, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + + // Desired view properties + _model: { + x: xScale.calculateBarX(this.chart.data.datasets.length, this.index, index), + y: reset ? yScalePoint : yScale.getPixelForValue(this.getDataset().data[index]), + + // Tooltip + label: this.chart.data.labels[index], + datasetLabel: this.getDataset().label, + + // Appearance + base: yScale.calculateBarBase(this.index, index), + width: xScale.calculateBarWidth(this.chart.data.datasets.length), + 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), + + }, + }); + rectangle.pivot(); + }, this); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + helpers.each(this.getDataset().metaData, function(rectangle, index) { + rectangle.transition(easingDecimal).draw(); + }, this); + }, + + + + + + + + + // eachLine: function eachLine(callback) { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) { + // callback.call(this, dataset, datasetIndex); + // } + // }, this); + // }, + + // eachRectangle: function(callback) { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(element, index) { + // if (element instanceof Chart.Rectangle) { + // callback(element, index, dataset, datasetIndex); + // } + // }, this); + // }, this); + // }, + + // addLine: function addLine(dataset, datasetIndex) { + // if (dataset) { + // dataset.metaDataset = new Chart.Line({ + // _chart: this.chart.chart, + // _datasetIndex: datasetIndex, + // _points: dataset.metaData, + // }); + // } + // }, + + // addPoint: function addPoint(dataset, datasetIndex, index) { + // if (dataset) { + // dataset.metaData = dataset.metaData || new Array(this.chart.data.datasets[datasetIndex].data.length); + + // if (index < dataset.metaData.length) { + // dataset.metaData[index] = new Chart.Point({ + // _datasetIndex: datasetIndex, + // _index: index, + // _chart: this.chart.chart, + // _model: { + // x: 0, + // y: 0, + // }, + // }); + // } + // } + // }, + + + + // resetElements: function resetElements() { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.resetDatasetPoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (bars) + // this.resetDatasetRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + // resetDatasetPoints: function resetDatasetPoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); + + // var yScalePoint; + + // if (yScale.min < 0 && yScale.max < 0) { + // // all less than 0. use the top + // yScalePoint = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // yScalePoint = yScale.getPixelForValue(yScale.min); + // } else { + // yScalePoint = yScale.getPixelForValue(0); + // } + + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, //WTF + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, + + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScalePoint, + // }, + // }); + + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); + + // this.updateBezierControlPoints(dataset); + // }, + + // resetDatasetRectangles: function resetDatasetRectangles(dataset, datasetIndex) { + + // }, + + // resetElementAppearance: function(element, datasetIndex, index) { + // if (element instanceof Chart.Point) { + // this.updatePointElementAppearance(element, datasetIndex, index); + // } else if (element instanceof Chart.Rectangle) { + // this.updateRectangleElementAppearance(element, datasetIndex, index); + // } + // }, + + // updateElements: function updateElements() { + // // Update the lines + // this.updateLines(); + + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.updatePoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (bars) + // this.updateRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + // updateLines: function updateLines() { + // this.eachLine(function(dataset, datasetIndex) { + // var yScale = this.getScaleForId(dataset.yAxisID); + // var scaleBase; + + // if (yScale.min < 0 && yScale.max < 0) { + // scaleBase = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // scaleBase = yScale.getPixelForValue(yScale.min); + // } else { + // scaleBase = yScale.getPixelForValue(0); + // } + + // helpers.extend(dataset.metaDataset, { + // // Utility + // _scale: yScale, + // _datasetIndex: datasetIndex, + // // Data + // _children: dataset.metaData, + // // Model + // _model: { + // // Appearance + // tension: dataset.metaDataset.custom && dataset.metaDataset.custom.tension ? dataset.metaDataset.custom.tension : (dataset.tension || this.chart.options.elements.line.tension), + // backgroundColor: dataset.metaDataset.custom && dataset.metaDataset.custom.backgroundColor ? dataset.metaDataset.custom.backgroundColor : (dataset.backgroundColor || this.chart.options.elements.line.backgroundColor), + // borderWidth: dataset.metaDataset.custom && dataset.metaDataset.custom.borderWidth ? dataset.metaDataset.custom.borderWidth : (dataset.borderWidth || this.chart.options.elements.line.borderWidth), + // borderColor: dataset.metaDataset.custom && dataset.metaDataset.custom.borderColor ? dataset.metaDataset.custom.borderColor : (dataset.borderColor || this.chart.options.elements.line.borderColor), + // fill: dataset.metaDataset.custom && dataset.metaDataset.custom.fill ? dataset.metaDataset.custom.fill : (dataset.fill !== undefined ? dataset.fill : this.chart.options.elements.line.fill), + // skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.chart.options.elements.line.skipNull, + // drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.chart.options.elements.line.drawNull, + // // Scale + // scaleTop: yScale.top, + // scaleBottom: yScale.bottom, + // scaleZero: scaleBase, + // }, + // }); + + // dataset.metaDataset.pivot(); + // }); + // }, + + // updatePoints: function updatePoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); + + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, + + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // }, + // }); + + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); + + // this.updateBezierControlPoints(dataset); + // }, + + // updatePointElementAppearance: function updatePointElementAppearance(point, datasetIndex, index) { + // helpers.extend(point._model, { + // // Appearance + // tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension, + // radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].radius, index, this.chart.options.elements.point.radius), + // backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), + // borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBorderColor, index, this.chart.options.elements.point.borderColor), + // borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBorderWidth, index, this.chart.options.elements.point.borderWidth), + // skip: this.chart.data.datasets[datasetIndex].data[index] === null, + + // // Tooltip + // hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].hitRadius, index, this.chart.options.elements.point.hitRadius), + // }); + // }, + + + + // setElementHoverStyle: function setElementHoverStyle(element) { + // if (element instanceof Chart.Point) { + // this.setPointHoverStyle(element); + // } else if (element instanceof Chart.Rectangle) { + // this.setRectangleHoverStyle(element); + // } + // }, + + // setPointHoverStyle: function setPointHoverStyle(point) { + // var dataset = this.chart.data.datasets[point._datasetIndex]; + // var index = point._index; + + // point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + // point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); + // }, + + // setRectangleHoverStyle: function(rectangle) { + // var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + // var index = rectangle._index; + + // rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth); + // }, + + // updateBezierControlPoints: function updateBezierControlPoints(dataset) { + // // Update control points for the bezier curve + // helpers.each(dataset.metaData, function(point, index) { + // var controlPoints = helpers.splineCurve( + // this.previousPoint(dataset.metaData, index)._model, + // point._model, + // this.nextPoint(dataset.metaData, index)._model, + // point._model.tension + // ); + + // point._model.controlPointPreviousX = controlPoints.previous.x; + // point._model.controlPointNextX = controlPoints.next.x; + + // // Prevent the bezier going outside of the bounds of the graph + + // // Cap puter bezier handles to the upper/lower scale bounds + // if (controlPoints.next.y > this.chart.chartArea.bottom) { + // point._model.controlPointNextY = this.chart.chartArea.bottom; + // } else if (controlPoints.next.y < this.chart.chartArea.top) { + // point._model.controlPointNextY = this.chart.chartArea.top; + // } else { + // point._model.controlPointNextY = controlPoints.next.y; + // } + + // // Cap inner bezier handles to the upper/lower scale bounds + // if (controlPoints.previous.y > this.chart.chartArea.bottom) { + // point._model.controlPointPreviousY = this.chart.chartArea.bottom; + // } else if (controlPoints.previous.y < this.chart.chartArea.top) { + // point._model.controlPointPreviousY = this.chart.chartArea.top; + // } else { + // point._model.controlPointPreviousY = controlPoints.previous.y; + // } + + // // Now pivot the point for animation + // point.pivot(); + // }, this); + // }, + + // getElementsAtEvent: function(e) { + // var elementsArray = [], + // eventPosition = helpers.getRelativePosition(e), + // datasetIterator = function(dataset) { + // elementsArray.push(dataset.metaData[elementIndex]); + // }, + // elementIndex; + + // for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) { + // for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { + // if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + // helpers.each(this.chart.data.datasets, datasetIterator); + // } + // } + // } + + // return elementsArray.length ? elementsArray : []; + // }, + + // // Get the single element that was clicked on + // // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn + // getElementAtEvent: function(e) { + // var element = []; + // var eventPosition = helpers.getRelativePosition(e); + + // for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) { + // for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + // if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + // element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); + // return element; + // } + // } + // } + + // return []; + // }, + }); + + + +}).call(this); diff --git a/src/controllers/controller.canvas.rectangular.js b/src/controllers/controller.canvas.rectangular.js deleted file mode 100644 index 80da709a4..000000000 --- a/src/controllers/controller.canvas.rectangular.js +++ /dev/null @@ -1,9 +0,0 @@ -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - -}).call(this); diff --git a/src/controllers/controller.elements.rectangular.js b/src/controllers/controller.elements.rectangular.js deleted file mode 100644 index 469aa0188..000000000 --- a/src/controllers/controller.elements.rectangular.js +++ /dev/null @@ -1,427 +0,0 @@ -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - Chart.RectangularElementController = function(chart) { - this.chartInstance = chart; - }; - - Chart.RectangularElementController.prototype.getScaleForId = function getScaleForId(scaleID) { - return this.chartInstance.scales[scaleID]; - }; - - // 2 helper functions to get points in collections - Chart.RectangularElementController.prototype.nextPoint = function nextPoint(collection, index) { - return collection[index + 1] || collection[index]; - }; - Chart.RectangularElementController.prototype.previousPoint = function previousPoint(collection, index) { - return collection[index - 1] || collection[index]; - }; - - Chart.RectangularElementController.prototype.eachLine = function eachLine(callback) { - helpers.each(this.chartInstance.data.datasets, function(dataset, datasetIndex) { - if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) { - callback.call(this, dataset, datasetIndex) - } - }, this); - }; - - Chart.RectangularElementController.prototype.eachRectangle = function(callback) { - helpers.each(this.chartInstance.data.datasets, function(dataset, datasetIndex) { - helpers.each(dataset.metaData, function(element, index) { - if (element instanceof Chart.Rectangle) { - callback(element, index, dataset, datasetIndex); - } - }, this); - }, this); - }; - - Chart.RectangularElementController.prototype.addLine = function addLine(dataset, datasetIndex) { - if (dataset) { - dataset.metaDataset = new Chart.Line({ - _chart: this.chartInstance.chart, - _datasetIndex: datasetIndex, - _points: dataset.metaData, - }); - } - } - - Chart.RectangularElementController.prototype.addPoint = function addPoint(dataset, datasetIndex, index) { - if (dataset) { - dataset.metaData = dataset.metaData || new Array(this.chartInstance.data.datasets[datasetIndex].data.length); - - if (index < dataset.metaData.length) { - dataset.metaData[index] = new Chart.Point({ - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chartInstance.chart, - _model: { - x: 0, - y: 0, - }, - }) - } - } - }; - - Chart.RectangularElementController.prototype.addRectangle = function(dataset, datasetIndex, index) { - if (dataset) { - dataset.metaData = dataset.metaData || new Array(this.chartInstance.data.datasets[datasetIndex].data.length); - - if (index < dataset.metaData.length) { - dataset.metaData[index] = new Chart.Rectangle({ - _chart: this.chartInstance.chart, - _datasetIndex: datasetIndex, - _index: index, - }); - } - } - }; - - Chart.RectangularElementController.prototype.resetElements = function resetElements() { - helpers.each(this.chartInstance.data.datasets, function(dataset, datasetIndex) { - // All elements must be the same type for the given dataset so we are fine to check just the first one - if (dataset.metaData[0] instanceof Chart.Point) { - // Have points. Update all of them - this.resetDatasetPoints(dataset, datasetIndex); - } else if (dataset.metaData[0] instanceof Chart.Rectangle) { - // Have rectangles (bars) - this.resetDatasetRectangles(dataset, datasetIndex); - } - }, this); - }; - - Chart.RectangularElementController.prototype.resetDatasetPoints = function resetDatasetPoints(dataset, datasetIndex) { - helpers.each(dataset.metaData, function(point, index){ - var xScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].xAxisID); - var yScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].yAxisID); - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(point, { - // Utility - _chart: this.chartInstance.chart, //WTF - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPointPixelForValue(this.chartInstance.data.datasets[datasetIndex].data[index], index, datasetIndex), - y: yScalePoint, - }, - }); - - this.updatePointElementAppearance(point, datasetIndex, index); - }, this); - - this.updateBezierControlPoints(dataset); - }; - - Chart.RectangularElementController.prototype.resetDatasetRectangles = function resetDatasetRectangles(dataset, datasetIndex) { - helpers.each(dataset.metaData, function(rectangle, index) { - var xScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].xAxisID); - var yScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].yAxisID); - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(rectangle, { - // Utility - _chart: this.chartInstance.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.chartInstance.data.datasets.length, datasetIndex, index), - y: yScalePoint, - - // Tooltip - label: this.chartInstance.data.labels[index], - datasetLabel: this.chartInstance.data.datasets[datasetIndex].label, - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.chartInstance.data.datasets.length), - }, - }); - - this.updateRectangleElementAppearance(rectangle, datasetIndex, index); - rectangle.pivot(); - }, this); - }; - - Chart.RectangularElementController.prototype.resetElementAppearance = function(element, datasetIndex, index) { - if (element instanceof Chart.Point) { - this.updatePointElementAppearance(element, datasetIndex, index); - } else if (element instanceof Chart.Rectangle) { - this.updateRectangleElementAppearance(element, datasetIndex, index); - } - }; - - Chart.RectangularElementController.prototype.updateElements = function updateElements() { - // Update the lines - this.updateLines(); - - helpers.each(this.chartInstance.data.datasets, function(dataset, datasetIndex) { - // All elements must be the same type for the given dataset so we are fine to check just the first one - if (dataset.metaData[0] instanceof Chart.Point) { - // Have points. Update all of them - this.updatePoints(dataset, datasetIndex); - } else if (dataset.metaData[0] instanceof Chart.Rectangle) { - // Have rectangles (bars) - this.updateRectangles(dataset, datasetIndex); - } - }, this); - }; - - Chart.RectangularElementController.prototype.updateLines = function updateLines() { - this.eachLine(function(dataset, datasetIndex) { - var yScale = this.getScaleForId(dataset.yAxisID); - var scaleBase; - - if (yScale.min < 0 && yScale.max < 0) { - scaleBase = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - scaleBase = yScale.getPixelForValue(yScale.min); - } else { - scaleBase = yScale.getPixelForValue(0); - } - - helpers.extend(dataset.metaDataset, { - // Utility - _scale: yScale, - _datasetIndex: datasetIndex, - // Data - _children: dataset.metaData, - // Model - _model: { - // Appearance - tension: dataset.metaDataset.custom && dataset.metaDataset.custom.tension ? dataset.metaDataset.custom.tension : (dataset.tension || this.chartInstance.options.elements.line.tension), - backgroundColor: dataset.metaDataset.custom && dataset.metaDataset.custom.backgroundColor ? dataset.metaDataset.custom.backgroundColor : (dataset.backgroundColor || this.chartInstance.options.elements.line.backgroundColor), - borderWidth: dataset.metaDataset.custom && dataset.metaDataset.custom.borderWidth ? dataset.metaDataset.custom.borderWidth : (dataset.borderWidth || this.chartInstance.options.elements.line.borderWidth), - borderColor: dataset.metaDataset.custom && dataset.metaDataset.custom.borderColor ? dataset.metaDataset.custom.borderColor : (dataset.borderColor || this.chartInstance.options.elements.line.borderColor), - fill: dataset.metaDataset.custom && dataset.metaDataset.custom.fill ? dataset.metaDataset.custom.fill : (dataset.fill !== undefined ? dataset.fill : this.chartInstance.options.elements.line.fill), - skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.chartInstance.options.elements.line.skipNull, - drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.chartInstance.options.elements.line.drawNull, - // Scale - scaleTop: yScale.top, - scaleBottom: yScale.bottom, - scaleZero: scaleBase, - }, - }); - - dataset.metaDataset.pivot(); - }); - }; - - Chart.RectangularElementController.prototype.updatePoints = function updatePoints(dataset, datasetIndex) { - helpers.each(dataset.metaData, function(point, index) { - var xScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].xAxisID); - var yScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].yAxisID); - - helpers.extend(point, { - // Utility - _chart: this.chartInstance.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPointPixelForValue(this.chartInstance.data.datasets[datasetIndex].data[index], index, datasetIndex), - y: yScale.getPointPixelForValue(this.chartInstance.data.datasets[datasetIndex].data[index], index, datasetIndex), - }, - }); - - this.updatePointElementAppearance(point, datasetIndex, index); - }, this); - - this.updateBezierControlPoints(dataset); - }; - - Chart.RectangularElementController.prototype.updatePointElementAppearance = function updatePointElementAppearance(point, datasetIndex, index) { - helpers.extend(point._model, { - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.chartInstance.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].radius, index, this.chartInstance.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].pointBackgroundColor, index, this.chartInstance.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].pointBorderColor, index, this.chartInstance.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].pointBorderWidth, index, this.chartInstance.options.elements.point.borderWidth), - skip: this.chartInstance.data.datasets[datasetIndex].data[index] === null, - - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].hitRadius, index, this.chartInstance.options.elements.point.hitRadius), - }); - }; - - Chart.RectangularElementController.prototype.updateRectangles = function updateRectangles(dataset, datasetIndex) { - helpers.each(dataset.metaData, function(rectangle, index){ - var xScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].xAxisID); - var yScale = this.getScaleForId(this.chartInstance.data.datasets[datasetIndex].yAxisID); - - helpers.extend(rectangle, { - // Utility - _chart: this.chartInstance.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.chartInstance.data.datasets.length, datasetIndex, index), - y: yScale.calculateBarY(datasetIndex, index), - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.chartInstance.data.datasets.length), - - // Tooltip - label: this.chartInstance.data.labels[index], - datasetLabel: this.chartInstance.data.datasets[datasetIndex].label, - }, - }); - - this.updateRectangleElementAppearance(rectangle, datasetIndex, index); - rectangle.pivot(); - }, this); - }; - - Chart.RectangularElementController.prototype.updateRectangleElementAppearance = function updateRectangleElementAppearance(rectangle, datasetIndex, index) { - helpers.extend(rectangle._model, { - // Appearance - backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].backgroundColor, index, this.chartInstance.options.elements.rectangle.backgroundColor), - borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].borderColor, index, this.chartInstance.options.elements.rectangle.borderColor), - borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.chartInstance.data.datasets[datasetIndex].borderWidth, index, this.chartInstance.options.elements.rectangle.borderWidth), - }); - }; - - Chart.RectangularElementController.prototype.setElementHoverStyle = function setElementHoverStyle(element) { - if (element instanceof Chart.Point) { - this.setPointHoverStyle(element); - } else if (element instanceof Chart.Rectangle) { - this.setRectangleHoverStyle(element); - } - }; - - Chart.RectangularElementController.prototype.setPointHoverStyle = function setPointHoverStyle(point) { - var dataset = this.chartInstance.data.datasets[point._datasetIndex]; - var index = point._index; - - point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chartInstance.options.elements.point.hoverRadius); - point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); - }; - - Chart.RectangularElementController.prototype.setRectangleHoverStyle = function(rectangle) { - var dataset = this.chartInstance.data.datasets[rectangle._datasetIndex]; - var index = rectangle._index; - - rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth); - }; - - Chart.RectangularElementController.prototype.updateBezierControlPoints = function updateBezierControlPoints(dataset) { - // Update control points for the bezier curve - helpers.each(dataset.metaData, function(point, index) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset.metaData, index)._model, - point._model, - this.nextPoint(dataset.metaData, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; - - // Prevent the bezier going outside of the bounds of the graph - - // Cap puter bezier handles to the upper/lower scale bounds - if (controlPoints.next.y > this.chartInstance.chartArea.bottom) { - point._model.controlPointNextY = this.chartInstance.chartArea.bottom; - } else if (controlPoints.next.y < this.chartInstance.chartArea.top) { - point._model.controlPointNextY = this.chartInstance.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartInstance.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartInstance.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartInstance.chartArea.top) { - point._model.controlPointPreviousY = this.chartInstance.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - }; - - Chart.RectangularElementController.prototype.getElementsAtEvent = function(e) { - var elementsArray = [], - eventPosition = helpers.getRelativePosition(e), - datasetIterator = function(dataset) { - elementsArray.push(dataset.metaData[elementIndex]); - }, - elementIndex; - - for (var datasetIndex = 0; datasetIndex < this.chartInstance.data.datasets.length; datasetIndex++) { - for (elementIndex = 0; elementIndex < this.chartInstance.data.datasets[datasetIndex].metaData.length; elementIndex++) { - if (this.chartInstance.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { - helpers.each(this.chartInstance.data.datasets, datasetIterator); - } - } - } - - return elementsArray.length ? elementsArray : []; - }; - - // Get the single element that was clicked on - // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn - Chart.RectangularElementController.prototype.getElementAtEvent = function(e) { - var element = []; - var eventPosition = helpers.getRelativePosition(e); - - for (var datasetIndex = 0; datasetIndex < this.chartInstance.data.datasets.length; ++datasetIndex) { - for (var elementIndex = 0; elementIndex < this.chartInstance.data.datasets[datasetIndex].metaData.length; ++elementIndex) { - if (this.chartInstance.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { - element.push(this.chartInstance.data.datasets[datasetIndex].metaData[elementIndex]); - return element; - } - } - } - - return []; - }; - -}).call(this); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index dd2a02340..4c2535ddd 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -15,22 +15,21 @@ //Destroy method on the chart will remove the instance of the chart from this reference. Chart.instances = {}; + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + // The main controller of a chart Chart.Controller = function(instance) { this.chart = instance; - var config = instance.config; - this.data = config.data; - this.options = config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[config.type], config.options || {}); + this.config = instance.config; + this.data = this.config.data; + this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); this.id = helpers.uid(); - console.log(this.options); - //Add the chart instance to the global namespace Chart.instances[this.id] = this; - // Initialize is always called when a chart type is created - // By default it is a no op, but it should be extended - if (this.options.responsive) { this.resize(); } @@ -44,12 +43,19 @@ initialize: function initialize() { + // TODO + // If BeforeInit(this) doesn't return false, proceed + this.bindEvents(); this.buildScales(); + this.buildControllers(); this.resetElements(); this.initToolTip(); this.update(); + // TODO + // If AfterInit(this) doesn't return false, proceed + return this; }, @@ -111,12 +117,30 @@ Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); }, - resetElements: function resetElements() { - // This will loop through any data and do the appropriate element reset for the type + buildControllers: function() { + this.eachDataset(function(dataset, datasetIndex) { + var type = dataset.type || this.config.type; + if (dataset.controller) { + dataset.controller.updateIndex(datasetIndex); + return; + } + dataset.controller = new Chart.controllers[type](this, datasetIndex); + }); }, + + resetElements: function resetElements() { + this.eachDataset(function(dataset, datasetIndex) { + dataset.controller.reset(); + }); + }, + + update: function update(animationDuration) { // This will loop through any data and do the appropriate element update for the type Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + this.eachDataset(function(dataset, datasetIndex) { + dataset.controller.update(); + }); this.render(animationDuration); }, @@ -148,6 +172,25 @@ return this; }, + draw: function(ease) { + var easingDecimal = ease || 1; + this.clear(); + + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); + + // Draw each dataset via its respective controller + // TODO: needs support for reverse stacking (line chart) + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.controller.draw(ease); + }, this); + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + eachValue: function eachValue(callback) { helpers.each(this.data.datasets, function(dataset, datasetIndex) { helpers.each(dataset.data, callback, this, datasetIndex); @@ -164,6 +207,20 @@ helpers.each(this.data.datasets, callback, this); }, + // 2 helper functions to get next/previous elements in datasets + nextElement: function nextElement(datasets, index, loop) { + if (this.loop) { + return dataset[index + 1] || dataset[0]; + } + return datasets[index + 1] || datasets[index]; + }, + previousElement: function previousElement(datasets, index, loop) { + if (this.loop) { + return dataset[index - 1] || dataset[dataset.length - 1]; + } + return datasets[index - 1] || datasets[index]; + }, + // Get the single element that was clicked on // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw getElementAtEvent: function getElementAtEvent(e) { diff --git a/src/core/core.element.js b/src/core/core.element.js index d787a6336..bd7b8ad6b 100644 --- a/src/core/core.element.js +++ b/src/core/core.element.js @@ -7,6 +7,7 @@ previous = root.Chart, helpers = Chart.helpers; + Chart.elements = {}; Chart.Element = function(configuration) { helpers.extend(this, configuration); diff --git a/src/core/core.js b/src/core/core.js index d8037c611..e64f98d2b 100755 --- a/src/core/core.js +++ b/src/core/core.js @@ -58,13 +58,15 @@ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. Chart.helpers.retinaScale(this); - this.controller = new Chart.Controller(this); + if (config) { + this.controller = new Chart.Controller(this); + return this.controller; + } + + return this; - return this.controller; }; - var defaultColor = 'rgba(0,0,0,0.1)'; - //Globally expose the defaults to allow for user updating/changing Chart.defaults = { global: { @@ -77,7 +79,7 @@ animationDuration: 400, }, onClick: null, - defaultColor: defaultColor, + defaultColor: 'rgba(0,0,0,0.1)', // Element defaults defined in element extensions elements: {} diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 7c35df01d..f4b71a380 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -1,14 +1,3 @@ -/*! - * Chart.js - * http://chartjs.org/ - * Version: {{ version }} - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ - - (function() { "use strict"; @@ -23,7 +12,7 @@ borderColor: Chart.defaults.global.defaultColor, }; - Chart.Rectangle = Chart.Element.extend({ + Chart.elements.Rectangle = Chart.Element.extend({ draw: function() { var ctx = this._chart.ctx; @@ -92,4 +81,6 @@ }, }); + + }).call(this);