CartesianSeries.js revision f69d245bb21be88752420e834a6b6be37e9b525f
/**
* The CartesianSeries class creates a chart with horizontal and vertical axes.
*
* @class CartesianSeries
* @extends Base, Renderer
* @constructor
*/
Y.CartesianSeries = Y.Base.create("cartesianSeries", Y.Base, [Y.Renderer], {
/**
* @private
*/
_xDisplayName: null,
/**
* @private
*/
_yDisplayName: null,
/**
* @private
*/
_leftOrigin: null,
/**
* @private
*/
_bottomOrigin: null,
/**
* @private
*/
render: function()
{
this._setCanvas();
this.addListeners();
this.set("rendered", true);
this.validate();
},
/**
* @private
*/
addListeners: function()
{
var xAxis = this.get("xAxis"),
yAxis = this.get("yAxis");
if(xAxis)
{
xAxis.after("dataReady", Y.bind(this._xDataChangeHandler, this));
xAxis.after("dataUpdate", Y.bind(this._xDataChangeHandler, this));
}
if(yAxis)
{
yAxis.after("dataReady", Y.bind(this._yDataChangeHandler, this));
yAxis.after("dataUpdate", Y.bind(this._yDataChangeHandler, this));
}
this.after("xAxisChange", this._xAxisChangeHandler);
this.after("yAxisChange", this._yAxisChangeHandler);
this.after("stylesChange", function(e) {
var axesReady = this._updateAxisData();
if(axesReady)
{
this.draw();
}
});
this.after("widthChange", function(e) {
var axesReady = this._updateAxisData();
if(axesReady)
{
this.draw();
}
});
this.after("heightChange", function(e) {
var axesReady = this._updateAxisData();
if(axesReady)
{
this.draw();
}
});
this.after("visibleChange", this._toggleVisible);
},
/**
* @private
*/
_xAxisChangeHandler: function(e)
{
var xAxis = this.get("xAxis");
xAxis.after("dataReady", Y.bind(this._xDataChangeHandler, this));
xAxis.after("dataUpdate", Y.bind(this._xDataChangeHandler, this));
},
/**
* @private
*/
_yAxisChangeHandler: function(e)
{
var yAxis = this.get("yAxis");
yAxis.after("dataReady", Y.bind(this._yDataChangeHandler, this));
yAxis.after("dataUpdate", Y.bind(this._yDataChangeHandler, this));
},
/**
* @private
*/
GUID: "yuicartesianseries",
/**
* @private (protected)
*/
_xDataChangeHandler: function(event)
{
var axesReady = this._updateAxisData();
if(axesReady)
{
this.draw();
}
},
/**
* @private (protected)
*/
_yDataChangeHandler: function(event)
{
var axesReady = this._updateAxisData();
if(axesReady)
{
this.draw();
}
},
/**
* @private
*/
_updateAxisData: function()
{
var xAxis = this.get("xAxis"),
yAxis = this.get("yAxis"),
xKey = this.get("xKey"),
yKey = this.get("yKey"),
yData,
xData;
if(!xAxis || !yAxis || !xKey || !yKey)
{
return false;
}
xData = xAxis.getDataByKey(xKey);
yData = yAxis.getDataByKey(yKey);
if(!xData || !yData)
{
return false;
}
this.set("xData", xData.concat());
this.set("yData", yData.concat());
return true;
},
/**
* @private
*/
validate: function()
{
if((this.get("xData") && this.get("yData")) || this._updateAxisData())
{
this.draw();
}
},
/**
* @private
* Creates a <code>Graphic</code> instance.
*/
_setCanvas: function()
{
this.set("graphic", new Y.Graphic());
this.get("graphic").render(this.get("graph").get("contentBox"));
},
/**
* @private
*/
setAreaData: function()
{
var nextX, nextY,
graph = this.get("graph"),
w = graph.get("width"),
h = graph.get("height"),
xAxis = this.get("xAxis"),
yAxis = this.get("yAxis"),
xData = this.get("xData").concat(),
yData = this.get("yData").concat(),
xOffset = xAxis.getEdgeOffset(xData.length, w),
yOffset = yAxis.getEdgeOffset(yData.length, h),
padding = this.get("styles").padding,
leftPadding = padding.left,
topPadding = padding.top,
dataWidth = w - (leftPadding + padding.right + xOffset),
dataHeight = h - (topPadding + padding.bottom + yOffset),
xcoords = [],
ycoords = [],
xMax = xAxis.get("maximum"),
xMin = xAxis.get("minimum"),
yMax = yAxis.get("maximum"),
yMin = yAxis.get("minimum"),
xScaleFactor = dataWidth / (xMax - xMin),
yScaleFactor = dataHeight / (yMax - yMin),
dataLength,
direction = this.get("direction"),
i = 0,
xMarkerPlane = [],
yMarkerPlane = [],
xMarkerPlaneOffset = this.get("xMarkerPlaneOffset"),
yMarkerPlaneOffset = this.get("yMarkerPlaneOffset"),
graphic = this.get("graphic");
dataLength = xData.length;
xOffset *= 0.5;
yOffset *= 0.5;
//Assuming a vertical graph has a range/category for its vertical axis.
if(direction === "vertical")
{
yData = yData.reverse();
}
if(graphic)
{
graphic.setSize(w, h);
}
this._leftOrigin = Math.round(((0 - xMin) * xScaleFactor) + leftPadding + xOffset);
this._bottomOrigin = Math.round((dataHeight + topPadding + yOffset) - (0 - yMin) * yScaleFactor);
for (; i < dataLength; ++i)
{
nextX = Math.round((((xData[i] - xMin) * xScaleFactor) + leftPadding + xOffset));
nextY = Math.round(((dataHeight + topPadding + yOffset) - (yData[i] - yMin) * yScaleFactor));
xcoords.push(nextX);
ycoords.push(nextY);
xMarkerPlane.push({start:nextX - xMarkerPlaneOffset, end: nextX + xMarkerPlaneOffset});
yMarkerPlane.push({start:nextY - yMarkerPlaneOffset, end: nextY + yMarkerPlaneOffset});
}
this.set("xcoords", xcoords);
this.set("ycoords", ycoords);
this.set("xMarkerPlane", xMarkerPlane);
this.set("yMarkerPlane", yMarkerPlane);
},
/**
* @private (override)
*/
draw: function()
{
var graph = this.get("graph"),
w = graph.get("width"),
h = graph.get("height");
if(this.get("rendered"))
{
if((isFinite(w) && isFinite(h) && w > 0 && h > 0) && ((this.get("xData") && this.get("yData")) || this._updateAxisData()))
{
if(this._drawing)
{
this._callLater = true;
return;
}
this._drawing = true;
this._callLater = false;
this.setAreaData();
if(this.get("xcoords") && this.get("ycoords"))
{
this.drawSeries();
}
this._drawing = false;
if(this._callLater)
{
this.draw();
}
else
{
this._toggleVisible(this.get("visible"));
this.fire("drawingComplete");
}
}
}
},
/**
* @private
*/
_defaultPlaneOffset: 4,
/**
* @private
*/
_getDefaultStyles: function()
{
return {padding:{
top: 0,
left: 0,
right: 0,
bottom: 0
}};
},
/**
* @private
*/
_defaultLineColors:["#426ab3", "#d09b2c", "#000000", "#b82837", "#b384b5", "#ff7200", "#779de3", "#cbc8ba", "#7ed7a6", "#007a6c"],
/**
* @private
*/
_defaultFillColors:["#6084d0", "#eeb647", "#6c6b5f", "#d6484f", "#ce9ed1", "#ff9f3b", "#93b7ff", "#e0ddd0", "#94ecba", "#309687"],
/**
* @private
*/
_defaultBorderColors:["#205096", "#b38206", "#000000", "#94001e", "#9d6fa0", "#e55b00", "#5e85c9", "#adab9e", "#6ac291", "#006457"],
/**
* @private
*/
_defaultSliceColors: ["#66007f", "#a86f41", "#295454", "#996ab2", "#e8cdb7", "#90bdbd","#000000","#c3b8ca", "#968373", "#678585"],
/**
* @private
*/
_getDefaultColor: function(index, type)
{
var colors = {
line: this._defaultLineColors,
fill: this._defaultFillColors,
border: this._defaultBorderColors,
slice: this._defaultSliceColors
},
col = colors[type],
l = col.length;
index = index || 0;
if(index >= l)
{
index = index % l;
}
type = type || "fill";
return colors[type][index];
},
/**
* @private
*/
_toggleVisible: function(e)
{
var graphic = this.get("graphic"),
markers = this.get("markers"),
i = 0,
len,
visible = this.get("visible"),
marker;
if(graphic)
{
graphic.toggleVisible(visible);
}
if(markers)
{
len = markers.length;
for(; i < len; ++i)
{
marker = markers[i];
if(marker)
{
marker.toggleVisible(visible);
}
}
}
if(this._lineGraphic)
{
this._lineGraphic.toggleVisible(visible);
}
}
}, {
ATTRS: {
/**
* Name used for for displaying data related to the x-coordinate.
*
* @attribute xDisplayName
* @type String
*/
xDisplayName: {
getter: function()
{
return this._xDisplayName || this.get("xKey");
},
setter: function(val)
{
this._xDisplayName = val;
return val;
}
},
/**
* Name used for for displaying data related to the y-coordinate.
*
* @attribute yDisplayName
* @type String
*/
yDisplayName: {
getter: function()
{
return this._yDisplayName || this.get("yKey");
},
setter: function(val)
{
this._yDisplayName = val;
return val;
}
},
/**
* Name used for for displaying category data
*
* @attribute categoryDisplayName
* @type String
*/
categoryDisplayName: {
readOnly: true,
getter: function()
{
return this.get("direction") == "vertical" ? this.get("yDisplayName") : this.get("xDisplayName");
}
},
/**
* Name used for for displaying value data
*
* @attribute valueDisplayName
* @type String
*/
valueDisplayName: {
readOnly: true,
getter: function()
{
return this.get("direction") == "vertical" ? this.get("xDisplayName") : this.get("yDisplayName");
}
},
/**
* Type of series.
*
* @attribute type
* @type String
*/
type: {
value: "cartesian"
},
/**
* Order of this instance of this <code>type</code>.
*
* @attribute order
* @type Number
*/
order: {},
/**
* Order of the instance
*
* @attribute graphOrder
* @type Number
*/
graphOrder: {},
/**
* x coordinates for the series.
*
* @attribute xcoords
* @type Array
*/
xcoords: {},
/**
* y coordinates for the series
*
* @attribute ycoords
* @type Array
*/
ycoords: {},
/**
* Reference to the <code>Graph</code> in which the series is drawn into.
*
* @attribute graph
* @type Graph
*/
graph: {},
/**
* Reference to the <code>Axis</code> instance used for assigning
* x-values to the graph.
*
* @attribute xAxis
* @type Axis
*/
xAxis: {},
/**
* Reference to the <code>Axis</code> instance used for assigning
* y-values to the graph.
*
* @attribute yAxis
* @type Axis
*/
yAxis: {},
/**
* Indicates which array to from the hash of value arrays in
* the x-axis <code>Axis</code> instance.
*
* @attribute xKey
* @type String
*/
xKey: {},
/**
* Indicates which array to from the hash of value arrays in
* the y-axis <code>Axis</code> instance.
*
* @attribute yKey
* @type String
*/
yKey: {},
/**
* Array of x values for the series.
*
* @attribute xData
* @type Array
*/
xData: {},
/**
* Array of y values for the series.
*
* @attribute yData
* @type Array
*/
yData: {},
/**
* Indicates whether the Series has been through its initial set up.
*
* @attribute rendered
* @type Boolean
*/
rendered: {
value: false
},
/*
* Returns the width of the parent graph
*
* @attribute width
* @type Number
*/
width: {
readOnly: true,
getter: function()
{
this.get("graph").get("width");
}
},
/**
* Returns the height of the parent graph
*
* @attribute height
* @type Number
*/
height: {
readOnly: true,
getter: function()
{
this.get("graph").get("height");
}
},
/**
* Indicates whether to show the series
*
* @attribute visible
* @type Boolean
* @default true
*/
visible: {
value: true
},
/**
* Collection of area maps along the xAxis. Used to determine mouseover for multiple
* series.
*
* @attribute xMarkerPlane
* @type Array
*/
xMarkerPlane: {},
/**
* Collection of area maps along the yAxis. Used to determine mouseover for multiple
* series.
*
* @attribute yMarkerPlane
* @type Array
*/
yMarkerPlane: {},
/**
* Distance from a data coordinate to the left/right for setting a hotspot.
*
* @attribute xMarkerPlaneOffset
* @type Number
*/
xMarkerPlaneOffset: {
getter: function() {
var marker = this.get("styles").marker;
if(marker && marker.width && isFinite(marker.width))
{
return marker.width * 0.5;
}
return this._defaultPlaneOffset;
}
},
/**
* Distance from a data coordinate to the top/bottom for setting a hotspot.
*
* @attribute yMarkerPlaneOffset
* @type Number
*/
yMarkerPlaneOffset: {
getter: function() {
var marker = this.get("styles").marker;
if(marker && marker.height && isFinite(marker.height))
{
return marker.height * 0.5;
}
return this._defaultPlaneOffset;
}
},
/**
* Direction of the series
*
* @attribute direction
* @type String
*/
direction: {
value: "horizontal"
}
}
});