facebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskype
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
16 KiB
522 lines
16 KiB
/** |
|
* @class Ext.chart.series.Gauge |
|
* @extends Ext.chart.series.Series |
|
* |
|
* Creates a Gauge Chart. |
|
* |
|
* @example |
|
* Ext.create({ |
|
* xtype: 'polar', |
|
* renderTo: document.body, |
|
* width: 600, |
|
* height: 400, |
|
* store: { |
|
* fields: ['mph', 'fuel', 'temp', 'rpm'], |
|
* data: [{ |
|
* mph: 65, |
|
* fuel: 50, |
|
* temp: 150, |
|
* rpm: 6000 |
|
* }] |
|
* }, |
|
* series: { |
|
* type: 'gauge', |
|
* colors: ['#1F6D91', '#90BCC9'], |
|
* field: 'mph', |
|
* needle: true, |
|
* donut: 30 |
|
* } |
|
* }); |
|
*/ |
|
Ext.define('Ext.chart.series.Gauge', { |
|
alias: 'series.gauge', |
|
extend: 'Ext.chart.series.Polar', |
|
type: 'gauge', |
|
seriesType: 'pieslice', |
|
|
|
requires: [ |
|
'Ext.draw.sprite.Sector' |
|
], |
|
|
|
config: { |
|
/** |
|
* @cfg {String} angleField |
|
* @deprecated Use `field` directly |
|
* The store record field name to be used for the gauge angles. |
|
* The values bound to this field name must be positive real numbers. |
|
*/ |
|
angleField: null, |
|
|
|
/** |
|
* @cfg {String} field |
|
* The store record field name to be used for the gauge value. |
|
* The values bound to this field name must be positive real numbers. |
|
*/ |
|
field: null, |
|
|
|
/** |
|
* @cfg {Boolean} needle |
|
* If true, display the gauge as a needle, otherwise as a sector. |
|
*/ |
|
needle: false, |
|
|
|
/** |
|
* @cfg {Number} needleLengthRatio |
|
* @deprecated Use `needleLength` directly |
|
* Ratio of the length of needle compared to the radius of the entire disk. |
|
*/ |
|
needleLengthRatio: undefined, |
|
|
|
/** |
|
* @cfg {Number} needleLength |
|
* Percentage of the length of needle compared to the radius of the entire disk. |
|
*/ |
|
needleLength: 90, |
|
|
|
/** |
|
* @cfg {Number} needleWidth |
|
* Width of the needle in pixels. |
|
*/ |
|
needleWidth: 4, |
|
|
|
/** |
|
* @cfg {Number} donut |
|
* Percentage of the radius of the donut hole compared to the entire disk. |
|
*/ |
|
donut: 30, |
|
|
|
/** |
|
* @cfg {Boolean} showInLegend |
|
* Whether to add the gauge chart elements as legend items. |
|
*/ |
|
showInLegend: false, |
|
|
|
/** |
|
* @cfg {Number} value |
|
* Directly sets the displayed value of the gauge. |
|
* It is ignored if {@link #field} is provided. |
|
*/ |
|
value: null, |
|
|
|
/** |
|
* @cfg {Array} colors (required) |
|
* An array of color values which is used for the needle and the `sectors`. |
|
*/ |
|
colors: null, |
|
|
|
/** |
|
* @cfg {Array} sectors |
|
* Allows to paint sectors of different colors in the background of the gauge, |
|
* with optional labels. |
|
* |
|
* It can be an array of numbers (each between `minimum` and `maximum`) that |
|
* define the highest value of each sector. For N sectors, only (N-1) values are |
|
* needed because it is assumed that the first sector starts at `minimum` and the |
|
* last sector ends at `maximum`. Example: a water temperature gauge that is blue |
|
* below 20C, red above 80C, gray in-between, and with an orange needle... |
|
* |
|
* minimum: 0, |
|
* maximum: 100, |
|
* sectors: [20, 80], |
|
* colors: ['orange', 'blue', 'lightgray', 'red'] |
|
* |
|
* It can be also an array of objects, each with the following properties: |
|
* |
|
* @cfg {Number} sectors.start The starting value of the sector. If omitted, it |
|
* uses the previous sector's `end` value or the chart's `minimum`. |
|
* @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses |
|
* the `maximum` defined for the chart. |
|
* @cfg {String} sectors.label The label for this sector. Labels are styled using |
|
* the series' {@link Ext.chart.series.Series#label label} config. |
|
* @cfg {String} sectors.color The color of the sector. If omitted, it uses one |
|
* of the `colors` defined for the series or for the chart. |
|
* @cfg {Object} sectors.style An additional style object for the sector (for |
|
* instance to set the opacity or to draw a line of a different color around the |
|
* sector). |
|
* |
|
* minimum: 0, |
|
* maximum: 100, |
|
* sectors: [{ |
|
* end: 20, |
|
* label: 'Cold', |
|
* color: 'aqua' |
|
* }, |
|
* { |
|
* end: 80, |
|
* label: 'Temp.', |
|
* color: 'lightgray', |
|
* style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 } |
|
* }, |
|
* { |
|
* label: 'Hot', |
|
* color: 'tomato' |
|
* }] |
|
*/ |
|
sectors: null, |
|
|
|
/** |
|
* @cfg {Number} minimum |
|
* The minimum value of the gauge. |
|
*/ |
|
minimum: 0, |
|
|
|
/** |
|
* @cfg {Number} maximum |
|
* The maximum value of the gauge. |
|
*/ |
|
maximum: 100, |
|
|
|
rotation: 0, |
|
|
|
/** |
|
* @cfg {Number} totalAngle |
|
* The size of the sector that the series will occupy. |
|
*/ |
|
totalAngle: Math.PI / 2, |
|
|
|
rect: [0, 0, 1, 1], |
|
|
|
center: [0.5, 0.75], |
|
|
|
radius: 0.5, |
|
|
|
/** |
|
* @cfg {Boolean} wholeDisk Indicates whether to show the whole disk or only the marked part. |
|
*/ |
|
wholeDisk: false |
|
}, |
|
|
|
coordinateX: function () { |
|
return this.coordinate('X', 0, 2); |
|
}, |
|
|
|
coordinateY: function () { |
|
return this.coordinate('Y', 1, 2); |
|
}, |
|
|
|
updateNeedle: function(needle) { |
|
var me = this, |
|
sprites = me.getSprites(), |
|
angle = me.valueToAngle(me.getValue()); |
|
|
|
if (sprites && sprites.length) { |
|
sprites[0].setAttributes({ |
|
startAngle: (needle ? angle : 0), |
|
endAngle: angle, |
|
strokeOpacity: (needle ? 1 : 0), |
|
lineWidth: (needle ? me.getNeedleWidth() : 0) |
|
}); |
|
me.doUpdateStyles(); |
|
} |
|
}, |
|
|
|
themeColorCount: function() { |
|
var me = this, |
|
store = me.getStore(), |
|
count = store && store.getCount() || 0; |
|
return count + (me.getNeedle() ? 0 : 1); |
|
|
|
}, |
|
|
|
updateColors: function (colors, oldColors) { |
|
var me = this, |
|
sectors = me.getSectors(), |
|
sectorCount = sectors && sectors.length, |
|
sprites = me.getSprites(), |
|
newColors = Ext.Array.clone(colors), |
|
colorCount = colors && colors.length, |
|
i; |
|
|
|
if (!colorCount || !colors[0]) { |
|
return; |
|
} |
|
|
|
// Make sure the 'sectors' colors are not overridden. |
|
for (i = 0; i < sectorCount; i++) { |
|
newColors[i+1] = sectors[i].color || newColors[i+1] || colors[i%colorCount]; |
|
} |
|
|
|
// if (sprites.length) { |
|
sprites[0].setAttributes({ |
|
strokeStyle: newColors[0] |
|
}); |
|
// } |
|
|
|
this.setSubStyle({ |
|
fillStyle: newColors, |
|
strokeStyle: newColors |
|
}); |
|
this.doUpdateStyles(); |
|
}, |
|
|
|
updateAngleField: function (angleField) { |
|
this.setField(angleField); |
|
}, |
|
|
|
updateNeedleLengthRatio: function (needleLengthRatio) { |
|
this.setNeedleLength(needleLengthRatio * 100); |
|
}, |
|
|
|
updateRect: function (rect) { |
|
var wholeDisk = this.getWholeDisk(), |
|
halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2, |
|
donut = this.getDonut() / 100, |
|
width, height, radius; |
|
if (halfTotalAngle <= Math.PI / 2) { |
|
width = 2 * Math.sin(halfTotalAngle); |
|
height = 1 - donut * Math.cos(halfTotalAngle); |
|
} else { |
|
width = 2; |
|
height = 1 - Math.cos(halfTotalAngle); |
|
} |
|
|
|
radius = Math.min(rect[2] / width, rect[3] / height); |
|
this.setRadius(radius); |
|
this.setCenter([rect[2] / 2, radius + (rect[3] - height * radius) / 2]); |
|
}, |
|
|
|
updateCenter: function (center) { |
|
this.setStyle({ |
|
centerX: center[0], |
|
centerY: center[1], |
|
rotationCenterX: center[0], |
|
rotationCenterY: center[1] |
|
}); |
|
this.doUpdateStyles(); |
|
}, |
|
|
|
updateRotation: function (rotation) { |
|
this.setStyle({ |
|
rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2 |
|
}); |
|
this.doUpdateStyles(); |
|
}, |
|
|
|
doUpdateShape: function (radius, donut) { |
|
var endRhoArray, |
|
sectors = this.getSectors(), |
|
sectorCount = (sectors && sectors.length) || 0, |
|
needleLength = this.getNeedleLength() / 100; |
|
|
|
// Initialize an array that contains the endRho for each sprite. |
|
// The first sprite is for the needle, the others for the gauge background sectors. |
|
// Note: SubStyle arrays are handled in series.getStyleByIndex(). |
|
endRhoArray = [radius * needleLength, radius]; |
|
while (sectorCount --) { |
|
endRhoArray.push(radius); |
|
} |
|
|
|
this.setSubStyle({ |
|
endRho: endRhoArray, |
|
startRho: radius / 100 * donut |
|
}); |
|
this.doUpdateStyles(); |
|
}, |
|
|
|
updateRadius: function (radius) { |
|
var donut = this.getDonut(); |
|
this.doUpdateShape(radius, donut); |
|
}, |
|
|
|
updateDonut: function (donut) { |
|
var radius = this.getRadius(); |
|
this.doUpdateShape(radius, donut); |
|
}, |
|
|
|
valueToAngle: function(value) { |
|
value = this.applyValue(value); |
|
return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum()); |
|
}, |
|
|
|
applyValue: function (value) { |
|
return Math.min(this.getMaximum(), Math.max(value, this.getMinimum())); |
|
}, |
|
|
|
updateValue: function (value) { |
|
var me = this, |
|
needle = me.getNeedle(), |
|
angle = me.valueToAngle(value), |
|
sprites = me.getSprites(); |
|
|
|
sprites[0].rendererData.value = value; |
|
sprites[0].setAttributes({ |
|
startAngle: (needle ? angle : 0), |
|
endAngle: angle |
|
}); |
|
me.doUpdateStyles(); |
|
}, |
|
|
|
processData: function () { |
|
var me = this, |
|
store = me.getStore(), |
|
axis, min, max, |
|
fx, fxDuration, |
|
record = store && store.first(), |
|
field, value; |
|
|
|
if (record) { |
|
field = me.getField(); |
|
if (field) { |
|
value = record.get(field); |
|
} |
|
} |
|
|
|
if (axis = me.getXAxis()) { |
|
min = axis.getMinimum(); |
|
max = axis.getMaximum(); |
|
// Animating the axis here can lead to weird looking results. |
|
fx = axis.getSprites()[0].fx; |
|
fxDuration = fx.getDuration(); |
|
fx.setDuration(0); |
|
if (Ext.isNumber(min)) { |
|
me.setMinimum(min); |
|
} else { |
|
axis.setMinimum(me.getMinimum()); |
|
} |
|
if (Ext.isNumber(max)) { |
|
me.setMaximum(max); |
|
} else { |
|
axis.setMaximum(me.getMaximum()); |
|
} |
|
fx.setDuration(fxDuration); |
|
} |
|
if (!Ext.isNumber(value)) { |
|
value = me.getMinimum(); |
|
} |
|
me.setValue(value); |
|
}, |
|
|
|
getDefaultSpriteConfig: function () { |
|
return { |
|
type: this.seriesType, |
|
renderer: this.getRenderer(), |
|
fx: { |
|
customDurations: { |
|
translationX: 0, |
|
translationY: 0, |
|
rotationCenterX: 0, |
|
rotationCenterY: 0, |
|
centerX: 0, |
|
centerY: 0, |
|
startRho: 0, |
|
endRho: 0, |
|
baseRotation: 0 |
|
} |
|
} |
|
}; |
|
}, |
|
|
|
normalizeSectors: function(sectors) { |
|
// Make sure all the sectors in the array have a legit start and end. |
|
// Note: the array is modified in-place. |
|
var me = this, |
|
sectorCount = (sectors && sectors.length) || 0, |
|
i, value, start, end; |
|
|
|
if (sectorCount) { |
|
for (i = 0; i < sectorCount; i++) { |
|
value = sectors[i]; |
|
if (typeof value === 'number') { |
|
sectors[i] = { |
|
start: (i > 0 ? sectors[i-1].end : me.getMinimum()), |
|
end: Math.min(value, me.getMaximum()) |
|
}; |
|
if (i == (sectorCount - 1) && sectors[i].end < me.getMaximum()) { |
|
sectors[i+1] = { |
|
start: sectors[i].end, |
|
end: me.getMaximum() |
|
}; |
|
} |
|
} else { |
|
if (typeof value.start === 'number') { |
|
start = Math.max(value.start, me.getMinimum()); |
|
} else { |
|
start = (i > 0 ? sectors[i-1].end : me.getMinimum()); |
|
} |
|
if (typeof value.end === 'number') { |
|
end = Math.min(value.end, me.getMaximum()); |
|
} else { |
|
end = me.getMaximum(); |
|
} |
|
sectors[i].start = start; |
|
sectors[i].end = end; |
|
} |
|
} |
|
} else { |
|
sectors = [{ |
|
start: me.getMinimum(), |
|
end: me.getMaximum() |
|
}]; |
|
} |
|
return sectors; |
|
}, |
|
|
|
getSprites: function () { |
|
var me = this, |
|
store = me.getStore(), |
|
value = me.getValue(), |
|
i, ln; |
|
|
|
// The store must be initialized, or the value must be set |
|
if (!store && !Ext.isNumber(value)) { |
|
return []; |
|
} |
|
|
|
// Return cached sprites |
|
var chart = me.getChart(), |
|
animation = me.getAnimation() || chart && chart.getAnimation(), |
|
sprites = me.sprites, |
|
spriteIndex = 0, |
|
sprite, sectors, attr, rendererData, |
|
lineWidths = []; // Hack to avoid having the lineWidths overwritten by the one specified in the theme. |
|
// In fact, all the style properties from the needle and sectors should go to the series subStyle. |
|
|
|
if (sprites && sprites.length) { |
|
sprites[0].fx.setConfig(animation); |
|
return sprites; |
|
} |
|
|
|
rendererData = { |
|
store: store, |
|
field: me.getField(), |
|
value: value, |
|
series: me |
|
}; |
|
|
|
// Create needle sprite |
|
sprite = me.createSprite(); |
|
sprite.setAttributes({ |
|
zIndex: 10 |
|
}, true); |
|
sprite.rendererData = rendererData; |
|
sprite.rendererIndex = spriteIndex++; |
|
lineWidths.push(me.getNeedleWidth()); |
|
|
|
// Create background sprite(s) |
|
me.getLabel().getTemplate().setField(true); // Enable labels |
|
sectors = me.normalizeSectors(me.getSectors()); |
|
for (i = 0, ln = sectors.length; i < ln; i++) { |
|
attr = { |
|
startAngle: me.valueToAngle(sectors[i].start), |
|
endAngle: me.valueToAngle(sectors[i].end), |
|
label: sectors[i].label, |
|
fillStyle: sectors[i].color, |
|
strokeOpacity: 0, |
|
rotateLabels: false, |
|
doCallout: false, // Show labels inside sectors. |
|
labelOverflowPadding: -1 // Allow labels to overlap. |
|
}; |
|
Ext.apply(attr, sectors[i].style); |
|
sprite = me.createSprite(); |
|
sprite.rendererData = rendererData; |
|
sprite.rendererIndex = spriteIndex++; |
|
sprite.setAttributes(attr, true); |
|
lineWidths.push(attr.lineWidth); |
|
} |
|
me.setSubStyle({ lineWidth: lineWidths }); |
|
|
|
me.doUpdateStyles(); |
|
return sprites; |
|
} |
|
}); |
|
|
|
|