/**
* Abstract class that provides default styles for non-specified things.
* Should be sub-classed when creating new themes.
* For example:
*
* Ext.define('Ext.chart.theme.Custom', {
* extend: 'Ext.chart.theme.Base',
* singleton: true,
* alias: 'chart.theme.custom',
* config: {
* baseColor: '#ff9f00'
* }
* });
*
* Theme provided values will not override the values provided in an instance config.
* However, if a theme provided value is an object, it will be merged with the value
* from the instance config, unless the theme provided object has a '$default' key
* set to 'true'.
*
* Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
* that they should inherit a value from the corresponding CSS style provided by
* a framework theme. Additionally, one can use basic binary operators like multiplication,
* addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
*
* Important: the theme should not use the 'font' shorthand to specify the font of labels
* and other text elements of a chart. Instead, individual font properties should be used:
* 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
*/
Ext.define('Ext.chart.theme.Base', {
extend: 'Ext.chart.theme.BaseTheme',
mixins: {
factoryable: 'Ext.mixin.Factoryable'
},
requires: ['Ext.draw.Color'],
factoryConfig: {
type: 'chart.theme'
},
isTheme: true,
isBase: true,
config: {
/**
* @cfg {String/Ext.util.Color} baseColor
* The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
*/
baseColor: null,
/**
* @cfg {Array} colors
*
* Array of colors/gradients to be used by the theme.
* Defaults to {@link #colorDefaults}.
*/
colors: undefined,
/**
* @cfg {Object} gradients
*
* The gradient config to be used by series' sprites. E.g.:
*
* {
* type: 'linear',
* degrees: 90
* }
*
* Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
* and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
* The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
* will be generated by the theme based on the {@link #colors} config.
*/
gradients: null,
/**
* @cfg {Object} chart
* Theme defaults for the chart.
* Can apply to all charts or just a specific type of chart.
* For example:
*
* chart: {
* defaults: {
* background: 'lightgray'
* },
* polar: {
* background: 'green'
* }
* }
*
* The values from the chart.defaults and chart.*type* configs (where *type* is a valid
* chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or
* '{@link Ext.chart.PolarChart polar}') will be applied to corresponding chart configs.
* E.g., the chart.defaults.background config will set the
* {@link Ext.chart.AbstractChart#background} config of all charts, where the
* chart.cartesian.flipXY config will only set the
* {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
*/
chart: {
defaults: {
captions: {
title: {
docked: 'top',
padding: 5,
style: {
textAlign: 'center',
fontFamily: 'default',
fontWeight: '500',
fillStyle: 'black',
fontSize: 'default*1.6'
}
},
subtitle: {
docked: 'top',
style: {
textAlign: 'center',
fontFamily: 'default',
fontWeight: 'normal',
fillStyle: 'black',
fontSize: 'default*1.3'
}
},
credits: {
docked: 'bottom',
padding: 5,
style: {
textAlign: 'left',
fontFamily: 'default',
fontWeight: 'lighter',
fillStyle: 'black',
fontSize: 'default'
}
}
},
background: 'white'
}
},
/**
* @cfg {Object} axis
* Theme defaults for the axes.
* Can apply to all axes or only axes with a specific position.
* For example:
*
* axis: {
* defaults: {
* style: {strokeStyle: 'red'}
* },
* left: {
* title: {fillStyle: 'green'}
* }
* }
*
* The values from the axis.defaults and axis.*position* configs (where *position*
* is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
* applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
* E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
* config of all axes, where the axis.left.titleMargin config will only apply to the
* {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
*/
axis: {
defaults: {
label: {
x: 0,
y: 0,
textBaseline: 'middle',
textAlign: 'center',
fontSize: 'default',
fontFamily: 'default',
fontWeight: 'default',
fillStyle: 'black'
},
title: {
fillStyle: 'black',
fontSize: 'default*1.23',
fontFamily: 'default',
fontWeight: 'default'
},
style: {
strokeStyle: 'black'
},
grid: {
strokeStyle: 'rgb(221, 221, 221)'
}
},
top: {
style: {
textPadding: 5
}
},
bottom: {
style: {
textPadding: 5
}
}
},
/**
* @cfg {Object} series
* Theme defaults for the series.
* Can apply to all series or just a specific type of series.
* For example:
*
* series: {
* defaults: {
* style: {
* lineWidth: 2
* }
* },
* bar: {
* animation: {
* easing: 'bounceOut',
* duration: 1000
* }
* }
* }
*
* The values from the series.defaults and series.*type* configs (where *type*
* is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
* applied to corresponding series configs.
* E.g., the series.defaults.label config will apply to the
* {@link Ext.chart.series.Series#label} config of all series, where the series.line.step
* config will only apply to the
* {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
*/
series: {
defaults: {
label: {
fillStyle: 'black',
strokeStyle: 'none',
fontFamily: 'default',
fontWeight: 'default',
fontSize: 'default*1.077',
textBaseline: 'middle',
textAlign: 'center'
},
labelOverflowPadding: 5
}
},
/**
* @cfg {Object} sprites
* Default style for the custom chart sprites by type.
* For example:
*
* sprites: {
* text: {
* fontWeight: 300
* }
* }
*
* These sprite attribute overrides will apply to custom sprites of all charts
* specified using the {@link Ext.draw.Container#sprites} config.
* The overrides are specified by sprite type, e.g. sprites.text config
* tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
*/
sprites: {
text: {
fontSize: 'default',
fontWeight: 'default',
fontFamily: 'default',
fillStyle: 'black'
}
},
/**
* Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
* If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
* For additional details see {@link Ext.chart.AbstractChart#legend}.
* @cfg {Object} legend
* @cfg {Ext.chart.legend.sprite.Item} legend.item
* @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
*/
legend: {
label: {
fontSize: 14,
fontWeight: 'default',
fontFamily: 'default',
fillStyle: 'black'
},
border: {
lineWidth: 1,
radius: 4,
fillStyle: 'none',
strokeStyle: 'gray'
},
background: 'white'
},
/**
* @private
* An object with the following structure:
* {
* fillStyle: [color, color, ...],
* strokeStyle: [color, color, ...],
* ...
* }
* If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
*/
seriesThemes: undefined,
markerThemes: {
type: ['circle', 'cross', 'plus', 'square', 'triangle', 'diamond']
},
/**
* @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
*/
useGradients: false,
/**
* @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
*/
background: null
},
colorDefaults: [
'#94ae0a',
'#115fa6',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111'
],
constructor: function(config) {
this.initConfig(config);
this.resolveDefaults();
},
defaultRegEx: /^default([+\-/*]\d+(?:\.\d+)?)?$/,
defaultOperators: {
'*': function(v1, v2) {
return v1 * v2;
},
'+': function(v1, v2) {
return v1 + v2;
},
'-': function(v1, v2) {
return v1 - v2;
}
},
resolveChartDefaults: function() {
var chart = Ext.clone(this.getChart()),
chartType, captionName,
chartConfig, captionConfig;
for (chartType in chart) {
chartConfig = chart[chartType];
if ('captions' in chartConfig) {
for (captionName in chartConfig.captions) {
captionConfig = chartConfig.captions[captionName];
if (captionConfig) {
this.replaceDefaults(captionConfig.style);
}
}
}
}
this.setChart(chart);
},
resolveDefaults: function() {
var me = this;
Ext.onInternalReady(function() {
var sprites = Ext.clone(me.getSprites()),
legend = Ext.clone(me.getLegend()),
axis = Ext.clone(me.getAxis()),
series = Ext.clone(me.getSeries()),
div, key, config;
if (!me.superclass.defaults) {
div = Ext.getBody().createChild({
tag: 'div',
cls: me.defaultsDivCls
});
me.superclass.defaults = {
fontFamily: div.getStyle('fontFamily'),
fontWeight: div.getStyle('fontWeight'),
fontSize: parseFloat(div.getStyle('fontSize')),
fontVariant: div.getStyle('fontVariant'),
fontStyle: div.getStyle('fontStyle')
};
div.destroy();
}
me.resolveChartDefaults();
me.replaceDefaults(sprites.text);
me.setSprites(sprites);
me.replaceDefaults(legend.label);
me.setLegend(legend);
for (key in axis) {
config = axis[key];
me.replaceDefaults(config.label);
me.replaceDefaults(config.title);
}
me.setAxis(axis);
for (key in series) {
config = series[key];
me.replaceDefaults(config.label);
}
me.setSeries(series);
});
},
replaceDefaults: function(target) {
var me = this,
defaults = me.superclass.defaults,
defaultRegEx = me.defaultRegEx,
key, value, match, binaryFn;
if (Ext.isObject(target)) {
for (key in defaults) {
match = defaultRegEx.exec(target[key]);
if (match) {
value = defaults[key];
match = match[1];
if (match) {
binaryFn = me.defaultOperators[match.charAt(0)];
value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
}
target[key] = value;
}
}
}
},
applyBaseColor: function(baseColor) {
var midColor, midL;
if (baseColor) {
midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
midL = midColor.getHSL()[2];
if (midL < 0.15) {
midColor = midColor.createLighter(0.3);
}
else if (midL < 0.3) {
midColor = midColor.createLighter(0.15);
}
else if (midL > 0.85) {
midColor = midColor.createDarker(0.3);
}
else if (midL > 0.7) {
midColor = midColor.createDarker(0.15);
}
this.setColors([
midColor.createDarker(0.3).toString(),
midColor.createDarker(0.15).toString(),
midColor.toString(),
midColor.createLighter(0.12).toString(),
midColor.createLighter(0.24).toString(),
midColor.createLighter(0.31).toString()
]);
}
return baseColor;
},
applyColors: function(newColors) {
return newColors || this.colorDefaults;
},
updateUseGradients: function(useGradients) {
if (useGradients) {
this.updateGradients({
type: 'linear',
degrees: 90
});
}
},
updateBackground: function(background) {
var chart;
if (background) {
chart = this.getChart();
chart.defaults.background = background;
this.setChart(chart);
}
},
updateGradients: function(gradients) {
var colors = this.getColors(),
items = [],
gradient,
midColor, color,
i, ln;
if (Ext.isObject(gradients)) {
for (i = 0, ln = colors && colors.length || 0; i < ln; i++) {
midColor = Ext.util.Color.fromString(colors[i]);
if (midColor) {
color = midColor.createLighter(0.15).toString();
gradient = Ext.apply(Ext.Object.chain(gradients), {
stops: [
{
offset: 1,
color: midColor.toString()
},
{
offset: 0,
color: color.toString()
}
]
});
items.push(gradient);
}
}
this.setColors(items);
}
},
applySeriesThemes: function(newSeriesThemes) {
var colors, color;
// Init the 'colors' config with solid colors generated from the 'baseColor'.
this.getBaseColor();
// Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
// config was set to 'true'. This in turn updates the 'colors' config.
this.getUseGradients();
// Init the 'gradients' config normally. This also updates the 'colors' config.
this.getGradients();
colors = this.getColors(); // Final colors.
if (!newSeriesThemes) {
newSeriesThemes = {
fillStyle: Ext.Array.clone(colors),
strokeStyle: Ext.Array.map(colors, function(value) {
color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
return color.createDarker(0.15).toString();
})
};
}
return newSeriesThemes;
}
});