/**
* Describes a gauge needle as a shape defined in SVG path syntax.
*
* Note: this class and its subclasses are not supposed to be instantiated directly
* - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
* config instead. Needle instances are also not supposed to be moved
* between gauges.
*/
Ext.define('Ext.ux.gauge.needle.Abstract', {
mixins: [
'Ext.mixin.Factoryable'
],
alias: 'gauge.needle.abstract',
isNeedle: true,
config: {
/**
* The generator function for the needle's shape.
* Because the gauge component is resizable, and it is generally
* desirable to resize the needle along with the gauge, the needle's
* shape should have an ability to grow, typically non-uniformly,
* which necessitates a generator function that will update the needle's
* path, so that its proportions are appropriate for the current gauge size.
*
* The generator function is given two parameters: the inner and outer
* radius of the needle. For example, for a straight arrow, the path
* definition is expected to have the base of the needle at the origin
* - (0, 0) coordinates - and point downwards. The needle will be automatically
* translated to the center of the gauge and rotated to represent the current
* gauge {@link Ext.ux.gauge.Gauge#value value}.
*
* @param {Function} path The path generator function.
* @param {Number} path.innerRadius The function's first parameter.
* @param {Number} path.outerRadius The function's second parameter.
* @return {String} path.return The shape of the needle in the SVG path syntax returned by
* the generator function.
*/
path: null,
/**
* The inner radius of the needle. This works just like the `innerRadius`
* config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
* The default value is `25` to make sure the needle doesn't overlap with
* the value of the gauge shown at its center by default.
*
* @param {Number/String} [innerRadius=25]
*/
innerRadius: 25,
/**
* The outer radius of the needle. This works just like the `outerRadius`
* config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
*
* @param {Number/String} [outerRadius='100% - 20']
*/
outerRadius: '100% - 20',
/**
* The shape generated by the {@link #path} function is used as the value
* for the `d` attribute of the SVG `<path>` element. This element
* has the default class name of `.x-gauge-needle`, so that CSS can be used
* to give all gauge needles some common styling. To style a particular needle,
* one can use this config to add styles to the needle's `<path>` element directly,
* or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
* and style the needle from there.
*
* This config is not supposed to be updated manually, the styles should
* always be updated by the means of the `setStyle` call. For example,
* this is not allowed:
*
* gauge.getStyle().fill = 'red'; // WRONG!
* gauge.setStyle({ 'fill': 'red' }); // correct
*
* Subsequent calls to the `setStyle` will add to the styles set previously
* or overwrite their values, but won't remove them. If you'd like to style
* from a clean slate, setting the style to `null` first will remove the styles
* previously set:
*
* gauge.getNeedle().setStyle(null);
*
* If an SVG shape was produced by a designer rather than programmatically,
* in other words, the {@link #path} function returns the same shape regardless
* of the parameters it was given, the uniform scaling of said shape is the only
* option, if one wants to use gauges of different sizes. In this case,
* it's possible to specify the desired scale by using the `transform` style,
* for example:
*
* transform: 'scale(0.35)'
*
* @param {Object} style
*/
style: null,
/**
* @private
* @param {Number} radius
*/
radius: 0,
/**
* @private
* Expected in the initial config, required during construction.
* @param {Ext.ux.gauge.Gauge} gauge
*/
gauge: null
},
constructor: function(config) {
this.initConfig(config);
},
applyInnerRadius: function(innerRadius) {
return this.getGauge().getRadiusFn(innerRadius);
},
applyOuterRadius: function(outerRadius) {
return this.getGauge().getRadiusFn(outerRadius);
},
updateRadius: function() {
this.regeneratePath();
},
setTransform: function(centerX, centerY, rotation) {
var needleGroup = this.getNeedleGroup();
needleGroup.setStyle(
'transform',
'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)'
);
},
applyPath: function(path) {
return Ext.isFunction(path) ? path : null;
},
updatePath: function(path) {
this.regeneratePath(path);
},
regeneratePath: function(path) {
path = path || this.getPath();
// eslint-disable-next-line vars-on-top
var me = this,
radius = me.getRadius(),
inner = me.getInnerRadius()(radius),
outer = me.getOuterRadius()(radius),
d = outer > inner ? path(inner, outer) : '';
me.getNeedlePath().dom.setAttribute('d', d);
},
getNeedleGroup: function() {
var gauge = this.getGauge(),
group = this.needleGroup;
// The gauge positions the needle by calling its `setTransform` method,
// which applies a transformation to the needle's group, that contains
// the actual path element. This is done because we need the ability to
// transform the path independently from it's position in the gauge.
// For example, if the needle has to be made bigger, is shouldn't be
// part of the transform that centers it in the gauge and rotates it
// to point at the current value.
if (!group) {
group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
gauge.getSvg().appendChild(group);
}
return group;
},
getNeedlePath: function() {
var me = this,
pathElement = me.pathElement;
if (!pathElement) {
pathElement = me.pathElement =
Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
me.getNeedleGroup().appendChild(pathElement);
}
return pathElement;
},
updateStyle: function(style) {
var pathElement = this.getNeedlePath();
// Note that we are setting the `style` attribute, e.g `style="fill: red"`,
// instead of path attributes individually, e.g. `fill="red"` because
// the attribute styles defined in CSS classes will override the values
// of attributes set on the elements individually.
if (Ext.isObject(style)) {
pathElement.setStyle(style);
}
else {
pathElement.dom.removeAttribute('style');
}
},
destroy: function() {
var me = this;
me.pathElement = Ext.destroy(me.pathElement);
me.needleGroup = Ext.destroy(me.needleGroup);
me.setGauge(null);
}
});