/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.charting.Theme"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.charting.Theme"] = true;
dojo.provide("dojox.charting.Theme");
dojo.require("dojox.color");
dojo.require("dojox.color.Palette");
dojo.require("dojox.lang.utils");
dojo.require("dojox.gfx.gradutils");
dojo.declare("dojox.charting.Theme", null, {
// summary:
// A Theme is a pre-defined object, primarily JSON-based, that makes up the definitions to
// style a chart.
//
// description:
// While you can set up style definitions on a chart directly (usually through the various add methods
// on a dojox.charting.Chart2D object), a Theme simplifies this manual setup by allowing you to
// pre-define all of the various visual parameters of each element in a chart.
//
// Most of the properties of a Theme are straight-forward; if something is line-based (such as
// an axis or the ticks on an axis), they will be defined using basic stroke parameters. Likewise,
// if an element is primarily block-based (such as the background of a chart), it will be primarily
// fill-based.
//
// In addition (for convenience), a Theme definition does not have to contain the entire JSON-based
// structure. Each theme is built on top of a default theme (which serves as the basis for the theme
// "GreySkies"), and is mixed into the default theme object. This allows you to create a theme based,
// say, solely on colors for data series.
//
// Defining a new theme is relatively easy; see any of the themes in dojox.charting.themes for examples
// on how to define your own.
//
// When you set a theme on a chart, the theme itself is deep-cloned. This means that you cannot alter
// the theme itself after setting the theme value on a chart, and expect it to change your chart. If you
// are looking to make alterations to a theme for a chart, the suggestion would be to create your own
// theme, based on the one you want to use, that makes those alterations before it is applied to a chart.
//
// Finally, a Theme contains a number of functions to facilitate rendering operations on a chart--the main
// helper of which is the ~next~ method, in which a chart asks for the information for the next data series
// to be rendered.
//
// A note on colors:
// The Theme constructor was on the use of dojox.color.Palette (in general) for creating a visually distinct
// set of colors for usage in a chart. A palette is usually comprised of 5 different color definitions, and
// no more. If you have a need to render a chart with more than 5 data elements, you can simply "push"
// new color definitions into the theme's .color array. Make sure that you do that with the actual
// theme object from a Chart, and not in the theme itself (i.e. either do that before using .setTheme
// on a chart).
//
// example:
// The default theme (and structure) looks like so:
// | // all objects are structs used directly in dojox.gfx
// | chart:{
// | stroke: null,
// | fill: "white",
// | pageStyle: null // suggested page style as an object suitable for dojo.style()
// | },
// | plotarea:{
// | stroke: null,
// | fill: "white"
// | },
// | axis:{
// | stroke: { // the axis itself
// | color: "#333",
// | width: 1
// | },
// | tick: { // used as a foundation for all ticks
// | color: "#666",
// | position: "center",
// | font: "normal normal normal 7pt Tahoma", // labels on axis
// | fontColor: "#333" // color of labels
// | },
// | majorTick: { // major ticks on axis, and used for major gridlines
// | width: 1,
// | length: 6
// | },
// | minorTick: { // minor ticks on axis, and used for minor gridlines
// | width: 0.8,
// | length: 3
// | },
// | microTick: { // minor ticks on axis, and used for minor gridlines
// | width: 0.5,
// | length: 1
// | }
// | },
// | series: {
// | stroke: {width: 1.5, color: "#333"}, // line
// | outline: {width: 0.1, color: "#ccc"}, // outline
// | //shadow: {dx: 1, dy: 1, width: 2, color: [0, 0, 0, 0.3]},
// | shadow: null, // no shadow
// | fill: "#ccc", // fill, if appropriate
// | font: "normal normal normal 8pt Tahoma", // if there's a label
// | fontColor: "#000" // color of labels
// | labelWiring: {width: 1, color: "#ccc"}, // connect marker and target data item(slice, column, bar...)
// | },
// | marker: { // any markers on a series
// | symbol: "m-3,3 l3,-6 3,6 z", // symbol
// | stroke: {width: 1.5, color: "#333"}, // stroke
// | outline: {width: 0.1, color: "#ccc"}, // outline
// | shadow: null, // no shadow
// | fill: "#ccc", // fill if needed
// | font: "normal normal normal 8pt Tahoma", // label
// | fontColor: "#000"
// | }
//
// example:
// Defining a new theme is pretty simple:
// | dojox.charting.themes.Grasslands = new dojox.charting.Theme({
// | colors: [ "#70803a", "#dde574", "#788062", "#b1cc5d", "#eff2c2" ]
// | });
// |
// | myChart.setTheme(dojox.charting.themes.Grasslands);
shapeSpaces: {shape: 1, shapeX: 1, shapeY: 1},
constructor: function(kwArgs){
// summary:
// Initialize a theme using the keyword arguments. Note that the arguments
// look like the example (above), and may include a few more parameters.
kwArgs = kwArgs || {};
// populate theme with defaults updating them if needed
var def = dojox.charting.Theme.defaultTheme;
dojo.forEach(["chart", "plotarea", "axis", "series", "marker"], function(name){
this[name] = dojo.delegate(def[name], kwArgs[name]);
}, this);
// personalize theme
if(kwArgs.seriesThemes && kwArgs.seriesThemes.length){
this.colors = null;
this.seriesThemes = kwArgs.seriesThemes.slice(0);
}else{
this.seriesThemes = null;
this.colors = (kwArgs.colors || dojox.charting.Theme.defaultColors).slice(0);
}
this.markerThemes = null;
if(kwArgs.markerThemes && kwArgs.markerThemes.length){
this.markerThemes = kwArgs.markerThemes.slice(0);
}
this.markers = kwArgs.markers ? dojo.clone(kwArgs.markers) : dojo.delegate(dojox.charting.Theme.defaultMarkers);
// set flags
this.noGradConv = kwArgs.noGradConv;
this.noRadialConv = kwArgs.noRadialConv;
if(kwArgs.reverseFills){
this.reverseFills();
}
// private housekeeping
this._current = 0;
this._buildMarkerArray();
},
clone: function(){
// summary:
// Clone the current theme.
// returns: dojox.charting.Theme
// The cloned theme; any alterations made will not affect the original.
var theme = new dojox.charting.Theme({
// theme components
chart: this.chart,
plotarea: this.plotarea,
axis: this.axis,
series: this.series,
marker: this.marker,
// individual arrays
colors: this.colors,
markers: this.markers,
seriesThemes: this.seriesThemes,
markerThemes: this.markerThemes,
// flags
noGradConv: this.noGradConv,
noRadialConv: this.noRadialConv
});
// copy custom methods
dojo.forEach(
["clone", "clear", "next", "skip", "addMixin", "post", "getTick"],
function(name){
if(this.hasOwnProperty(name)){
theme[name] = this[name];
}
},
this
);
return theme; // dojox.charting.Theme
},
clear: function(){
// summary:
// Clear and reset the internal pointer to start fresh.
this._current = 0;
},
next: function(elementType, mixin, doPost){
// summary:
// Get the next color or series theme.
// elementType: String?
// An optional element type (for use with series themes)
// mixin: Object?
// An optional object to mix into the theme.
// doPost: Boolean?
// A flag to post-process the results.
// returns: Object
// An object of the structure { series, marker, symbol }
var merge = dojox.lang.utils.merge, series, marker;
if(this.colors){
series = dojo.delegate(this.series);
marker = dojo.delegate(this.marker);
var color = new dojo.Color(this.colors[this._current % this.colors.length]), old;
// modify the stroke
if(series.stroke && series.stroke.color){
series.stroke = dojo.delegate(series.stroke);
old = new dojo.Color(series.stroke.color);
series.stroke.color = new dojo.Color(color);
series.stroke.color.a = old.a;
}else{
series.stroke = {color: color};
}
if(marker.stroke && marker.stroke.color){
marker.stroke = dojo.delegate(marker.stroke);
old = new dojo.Color(marker.stroke.color);
marker.stroke.color = new dojo.Color(color);
marker.stroke.color.a = old.a;
}else{
marker.stroke = {color: color};
}
// modify the fill
if(!series.fill || series.fill.type){
series.fill = color;
}else{
old = new dojo.Color(series.fill);
series.fill = new dojo.Color(color);
series.fill.a = old.a;
}
if(!marker.fill || marker.fill.type){
marker.fill = color;
}else{
old = new dojo.Color(marker.fill);
marker.fill = new dojo.Color(color);
marker.fill.a = old.a;
}
}else{
series = this.seriesThemes ?
merge(this.series, this.seriesThemes[this._current % this.seriesThemes.length]) :
this.series;
marker = this.markerThemes ?
merge(this.marker, this.markerThemes[this._current % this.markerThemes.length]) :
series;
}
var symbol = marker && marker.symbol || this._markers[this._current % this._markers.length];
var theme = {series: series, marker: marker, symbol: symbol};
// advance the counter
++this._current;
if(mixin){
theme = this.addMixin(theme, elementType, mixin);
}
if(doPost){
theme = this.post(theme, elementType);
}
return theme; // Object
},
skip: function(){
// summary:
// Skip the next internal color.
++this._current;
},
addMixin: function(theme, elementType, mixin, doPost){
// summary:
// Add a mixin object to the passed theme and process.
// theme: dojox.charting.Theme
// The theme to mixin to.
// elementType: String
// The type of element in question. Can be "line", "bar" or "circle"
// mixin: Object|Array
// The object or objects to mix into the theme.
// doPost: Boolean
// If true, run the new theme through the post-processor.
// returns: dojox.charting.Theme
// The new theme.
if(dojo.isArray(mixin)){
dojo.forEach(mixin, function(m){
theme = this.addMixin(theme, elementType, m);
}, this);
}else{
var t = {};
if("color" in mixin){
if(elementType == "line" || elementType == "area"){
dojo.setObject("series.stroke.color", mixin.color, t);
dojo.setObject("marker.stroke.color", mixin.color, t);
}else{
dojo.setObject("series.fill", mixin.color, t);
}
}
dojo.forEach(["stroke", "outline", "shadow", "fill", "font", "fontColor", "labelWiring"], function(name){
var markerName = "marker" + name.charAt(0).toUpperCase() + name.substr(1),
b = markerName in mixin;
if(name in mixin){
dojo.setObject("series." + name, mixin[name], t);
if(!b){
dojo.setObject("marker." + name, mixin[name], t);
}
}
if(b){
dojo.setObject("marker." + name, mixin[markerName], t);
}
});
if("marker" in mixin){
t.symbol = mixin.marker;
}
theme = dojox.lang.utils.merge(theme, t);
}
if(doPost){
theme = this.post(theme, elementType);
}
return theme; // dojox.charting.Theme
},
post: function(theme, elementType){
// summary:
// Process any post-shape fills.
// theme: dojox.charting.Theme
// The theme to post process with.
// elementType: String
// The type of element being filled. Can be "bar" or "circle".
// returns: dojox.charting.Theme
// The post-processed theme.
var fill = theme.series.fill, t;
if(!this.noGradConv && this.shapeSpaces[fill.space] && fill.type == "linear"){
if(elementType == "bar"){
// transpose start and end points
t = {
x1: fill.y1,
y1: fill.x1,
x2: fill.y2,
y2: fill.x2
};
}else if(!this.noRadialConv && fill.space == "shape" && (elementType == "slice" || elementType == "circle")){
// switch to radial
t = {
type: "radial",
cx: 0,
cy: 0,
r: 100
};
}
if(t){
return dojox.lang.utils.merge(theme, {series: {fill: t}});
}
}
return theme; // dojox.charting.Theme
},
getTick: function(name, mixin){
// summary:
// Calculates and merges tick parameters.
// name: String
// Tick name, can be "major", "minor", or "micro".
// mixin: Object?
// Optional object to mix in to the tick.
var tick = this.axis.tick, tickName = name + "Tick";
merge = dojox.lang.utils.merge;
if(tick){
if(this.axis[tickName]){
tick = merge(tick, this.axis[tickName]);
}
}else{
tick = this.axis[tickName];
}
if(mixin){
if(tick){
if(mixin[tickName]){
tick = merge(tick, mixin[tickName]);
}
}else{
tick = mixin[tickName];
}
}
return tick; // Object
},
inspectObjects: function(f){
dojo.forEach(["chart", "plotarea", "axis", "series", "marker"], function(name){
f(this[name]);
}, this);
if(this.seriesThemes){
dojo.forEach(this.seriesThemes, f);
}
if(this.markerThemes){
dojo.forEach(this.markerThemes, f);
}
},
reverseFills: function(){
this.inspectObjects(function(o){
if(o && o.fill){
o.fill = dojox.gfx.gradutils.reverse(o.fill);
}
});
},
addMarker:function(/*String*/ name, /*String*/ segment){
// summary:
// Add a custom marker to this theme.
// example:
// | myTheme.addMarker("Ellipse", foo);
this.markers[name] = segment;
this._buildMarkerArray();
},
setMarkers:function(/*Object*/ obj){
// summary:
// Set all the markers of this theme at once. obj should be a
// dictionary of keys and path segments.
//
// example:
// | myTheme.setMarkers({ "CIRCLE": foo });
this.markers = obj;
this._buildMarkerArray();
},
_buildMarkerArray: function(){
this._markers = [];
for(var p in this.markers){
this._markers.push(this.markers[p]);
}
}
});
/*=====
dojox.charting.Theme.__DefineColorArgs = function(num, colors, hue, saturation, low, high, base, generator){
// summary:
// The arguments object that can be passed to define colors for a theme.
// num: Number?
// The number of colors to generate. Defaults to 5.
// colors: String[]|dojo.Color[]?
// A pre-defined set of colors; this is passed through to the Theme directly.
// hue: Number?
// A hue to base the generated colors from (a number from 0 - 359).
// saturation: Number?
// If a hue is passed, this is used for the saturation value (0 - 100).
// low: Number?
// An optional value to determine the lowest value used to generate a color (HSV model)
// high: Number?
// An optional value to determine the highest value used to generate a color (HSV model)
// base: String|dojo.Color?
// A base color to use if we are defining colors using dojox.color.Palette
// generator: String?
// The generator function name from dojox.color.Palette.
this.num = num;
this.colors = colors;
this.hue = hue;
this.saturation = saturation;
this.low = low;
this.high = high;
this.base = base;
this.generator = generator;
}
=====*/
dojo.mixin(dojox.charting.Theme, {
defaultMarkers: {
CIRCLE: "m-3,0 c0,-4 6,-4 6,0 m-6,0 c0,4 6,4 6,0",
SQUARE: "m-3,-3 l0,6 6,0 0,-6 z",
DIAMOND: "m0,-3 l3,3 -3,3 -3,-3 z",
CROSS: "m0,-3 l0,6 m-3,-3 l6,0",
X: "m-3,-3 l6,6 m0,-6 l-6,6",
TRIANGLE: "m-3,3 l3,-6 3,6 z",
TRIANGLE_INVERTED: "m-3,-3 l3,6 3,-6 z"
},
defaultColors:[
// gray skies
"#54544c", "#858e94", "#6e767a", "#948585", "#474747"
],
defaultTheme: {
// all objects are structs used directly in dojox.gfx
chart:{
stroke: null,
fill: "white",
pageStyle: null,
titleGap: 20,
titlePos: "top",
titleFont: "normal normal bold 14pt Tahoma", // labels on axis
titleFontColor: "#333"
},
plotarea:{
stroke: null,
fill: "white"
},
// TODO: label rotation on axis
axis:{
stroke: { // the axis itself
color: "#333",
width: 1
},
tick: { // used as a foundation for all ticks
color: "#666",
position: "center",
font: "normal normal normal 7pt Tahoma", // labels on axis
fontColor: "#333", // color of labels
titleGap: 15,
titleFont: "normal normal normal 11pt Tahoma", // labels on axis
titleFontColor: "#333", // color of labels
titleOrientation: "axis" // "axis": facing the axis, "away": facing away
},
majorTick: { // major ticks on axis, and used for major gridlines
width: 1,
length: 6
},
minorTick: { // minor ticks on axis, and used for minor gridlines
width: 0.8,
length: 3
},
microTick: { // minor ticks on axis, and used for minor gridlines
width: 0.5,
length: 1
}
},
series: {
// used as a "main" theme for series, sThemes augment it
stroke: {width: 1.5, color: "#333"}, // line
outline: {width: 0.1, color: "#ccc"}, // outline
//shadow: {dx: 1, dy: 1, width: 2, color: [0, 0, 0, 0.3]},
shadow: null, // no shadow
fill: "#ccc", // fill, if appropriate
font: "normal normal normal 8pt Tahoma", // if there's a label
fontColor: "#000", // color of labels
labelWiring: {width: 1, color: "#ccc"} // connect marker and target data item(slice, column, bar...)
},
marker: { // any markers on a series
stroke: {width: 1.5, color: "#333"}, // stroke
outline: {width: 0.1, color: "#ccc"}, // outline
//shadow: {dx: 1, dy: 1, width: 2, color: [0, 0, 0, 0.3]},
shadow: null, // no shadow
fill: "#ccc", // fill if needed
font: "normal normal normal 8pt Tahoma", // label
fontColor: "#000"
}
},
defineColors: function(kwArgs){
// summary:
// Generate a set of colors for the theme based on keyword
// arguments.
// kwArgs: dojox.charting.Theme.__DefineColorArgs
// The arguments object used to define colors.
// returns: dojo.Color[]
// An array of colors for use in a theme.
//
// example:
// | var colors = dojox.charting.Theme.defineColors({
// | base: "#369",
// | generator: "compound"
// | });
//
// example:
// | var colors = dojox.charting.Theme.defineColors({
// | hue: 60,
// | saturation: 90,
// | low: 30,
// | high: 80
// | });
kwArgs = kwArgs || {};
var c = [], n = kwArgs.num || 5; // the number of colors to generate
if(kwArgs.colors){
// we have an array of colors predefined, so fix for the number of series.
var l = kwArgs.colors.length;
for(var i = 0; i < n; i++){
c.push(kwArgs.colors[i % l]);
}
return c; // dojo.Color[]
}
if(kwArgs.hue){
// single hue, generate a set based on brightness
var s = kwArgs.saturation || 100; // saturation
var st = kwArgs.low || 30;
var end = kwArgs.high || 90;
// we'd like it to be a little on the darker side.
var l = (end + st) / 2;
// alternately, use "shades"
return dojox.color.Palette.generate(
dojox.color.fromHsv(kwArgs.hue, s, l), "monochromatic"
).colors;
}
if(kwArgs.generator){
// pass a base color and the name of a generator
return dojox.color.Palette.generate(kwArgs.base, kwArgs.generator).colors;
}
return c; // dojo.Color[]
},
generateGradient: function(fillPattern, colorFrom, colorTo){
var fill = dojo.delegate(fillPattern);
fill.colors = [
{offset: 0, color: colorFrom},
{offset: 1, color: colorTo}
];
return fill;
},
generateHslColor: function(color, luminance){
color = new dojox.color.Color(color);
var hsl = color.toHsl(),
result = dojox.color.fromHsl(hsl.h, hsl.s, luminance);
result.a = color.a; // add missing opacity
return result;
},
generateHslGradient: function(color, fillPattern, lumFrom, lumTo){
color = new dojox.color.Color(color);
var hsl = color.toHsl(),
colorFrom = dojox.color.fromHsl(hsl.h, hsl.s, lumFrom),
colorTo = dojox.color.fromHsl(hsl.h, hsl.s, lumTo);
colorFrom.a = colorTo.a = color.a; // add missing opacity
return dojox.charting.Theme.generateGradient(fillPattern, colorFrom, colorTo); // Object
}
});
}