//>>built define("dojox/charting/Theme", ["dojo/_base/lang", "dojo/_base/array","dojo/_base/declare","dojo/_base/Color", "dojox/color/_base", "dojox/color/Palette", "dojox/lang/utils", "dojox/gfx/gradutils"], function(lang, arr, declare, Color, colorX, Palette, dlu, dgg){ var Theme = 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.Chart 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" // | }, // | indicator: { // | lineStroke: {width: 1.5, color: "#333"}, // line // | lineOutline: {width: 0.1, color: "#ccc"}, // line outline // | lineShadow: null, // no line shadow // | stroke: {width: 1.5, color: "#333"}, // label background stroke // | outline: {width: 0.1, color: "#ccc"}, // label background outline // | shadow: null, // no label background shadow // | fill: "#ccc", // label background fill // | radius: 3, // radius of the label background // | font: "normal normal normal 10pt Tahoma", // label font // | fontColor: "#000" // label color // | markerFill: "#ccc", // marker fill // | markerSymbol: "m-3,0 c0,-4 6,-4 6,0 m-6,0 c0,4 6,4 6,0", // marker symbol // | markerStroke: {width: 1.5, color: "#333"}, // marker stroke // | markerOutline: {width: 0.1, color: "#ccc"}, // marker outline // | markerShadow: null, // no marker shadow // | } // // 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 = Theme.defaultTheme; arr.forEach(["chart", "plotarea", "axis", "series", "marker", "indicator"], function(name){ this[name] = lang.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 || Theme.defaultColors).slice(0); } this.markerThemes = null; if(kwArgs.markerThemes && kwArgs.markerThemes.length){ this.markerThemes = kwArgs.markerThemes.slice(0); } this.markers = kwArgs.markers ? lang.clone(kwArgs.markers) : lang.delegate(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 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, indicator: this.indicator, seriesThemes: this.seriesThemes, markerThemes: this.markerThemes, // flags noGradConv: this.noGradConv, noRadialConv: this.noRadialConv }); // copy custom methods arr.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 = dlu.merge, series, marker; if(this.colors){ series = lang.delegate(this.series); marker = lang.delegate(this.marker); var color = new Color(this.colors[this._current % this.colors.length]), old; // modify the stroke if(series.stroke && series.stroke.color){ series.stroke = lang.delegate(series.stroke); old = new Color(series.stroke.color); series.stroke.color = new Color(color); series.stroke.color.a = old.a; }else{ series.stroke = {color: color}; } if(marker.stroke && marker.stroke.color){ marker.stroke = lang.delegate(marker.stroke); old = new Color(marker.stroke.color); marker.stroke.color = new 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 Color(series.fill); series.fill = new Color(color); series.fill.a = old.a; } if(!marker.fill || marker.fill.type){ marker.fill = color; }else{ old = new Color(marker.fill); marker.fill = new 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(lang.isArray(mixin)){ arr.forEach(mixin, function(m){ theme = this.addMixin(theme, elementType, m); }, this); }else{ var t = {}; if("color" in mixin){ if(elementType == "line" || elementType == "area"){ lang.setObject("series.stroke.color", mixin.color, t); lang.setObject("marker.stroke.color", mixin.color, t); }else{ lang.setObject("series.fill", mixin.color, t); } } arr.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){ lang.setObject("series." + name, mixin[name], t); if(!b){ lang.setObject("marker." + name, mixin[name], t); } } if(b){ lang.setObject("marker." + name, mixin[markerName], t); } }); if("marker" in mixin){ t.symbol = mixin.marker; } theme = dlu.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 dlu.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 = dlu.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){ arr.forEach(["chart", "plotarea", "axis", "series", "marker", "indicator"], function(name){ f(this[name]); }, this); if(this.seriesThemes){ arr.forEach(this.seriesThemes, f); } if(this.markerThemes){ arr.forEach(this.markerThemes, f); } }, reverseFills: function(){ this.inspectObjects(function(o){ if(o && o.fill){ o.fill = dgg.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; } =====*/ lang.mixin(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" }, indicator: { lineStroke: {width: 1.5, color: "#333"}, lineOutline: {width: 0.1, color: "#ccc"}, lineShadow: null, stroke: {width: 1.5, color: "#333"}, outline: {width: 0.1, color: "#ccc"}, shadow: null, fill : "#ccc", radius: 3, font: "normal normal normal 10pt Tahoma", fontColor: "#000", markerFill: "#ccc", markerSymbol: "m-3,0 c0,-4 6,-4 6,0 m-6,0 c0,4 6,4 6,0", markerStroke: {width: 1.5, color: "#333"}, markerOutline: {width: 0.1, color: "#ccc"}, markerShadow: null } }, 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 l, 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. 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 st = kwArgs.low || 30, end = kwArgs.high || 90; // we'd like it to be a little on the darker side. l = (end + st) / 2; // alternately, use "shades" return colorX.Palette.generate( colorX.fromHsv(kwArgs.hue, s, l), "monochromatic" ).colors; } if(kwArgs.generator){ // pass a base color and the name of a generator return colorX.Palette.generate(kwArgs.base, kwArgs.generator).colors; } return c; // dojo.Color[] }, generateGradient: function(fillPattern, colorFrom, colorTo){ var fill = lang.delegate(fillPattern); fill.colors = [ {offset: 0, color: colorFrom}, {offset: 1, color: colorTo} ]; return fill; }, generateHslColor: function(color, luminance){ color = new Color(color); var hsl = color.toHsl(), result = colorX.fromHsl(hsl.h, hsl.s, luminance); result.a = color.a; // add missing opacity return result; }, generateHslGradient: function(color, fillPattern, lumFrom, lumTo){ color = new Color(color); var hsl = color.toHsl(), colorFrom = colorX.fromHsl(hsl.h, hsl.s, lumFrom), colorTo = colorX.fromHsl(hsl.h, hsl.s, lumTo); colorFrom.a = colorTo.a = color.a; // add missing opacity return Theme.generateGradient(fillPattern, colorFrom, colorTo); // Object } }); return Theme; });