]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/panel/RRDChart.js
data/ProxmoxProxy: set responseType to undefined for XMLHTTPRequest
[proxmox-widget-toolkit.git] / src / panel / RRDChart.js
CommitLineData
9531c659
TL
1Ext.define('Proxmox.chart.axis.segmenter.NumericBase2', {
2 extend: 'Ext.chart.axis.segmenter.Numeric',
3 alias: 'segmenter.numericBase2',
4
5 // derived from the original numeric segmenter but using 2 instead of 10 as base
6 preferredStep: function(min, estStepSize) {
7 // Getting an order of magnitude of the estStepSize with a common logarithm.
8 let order = Math.floor(Math.log2(estStepSize));
9 let scale = Math.pow(2, order);
10
11 estStepSize /= scale;
12
13 // FIXME: below is not useful when using base 2 instead of base 10, we could
14 // just directly set estStepSize to 2
15 if (estStepSize <= 1) {
16 estStepSize = 1;
17 } else if (estStepSize < 2) {
18 estStepSize = 2;
19 }
20 return {
21 unit: {
22 // When passed estStepSize is less than 1, its order of magnitude
23 // is equal to -number_of_leading_zeros in the estStepSize.
24 fixes: -order, // Number of fractional digits.
25 scale: scale,
26 },
27 step: estStepSize,
28 };
29 },
30
31 /**
32 * Wraps the provided estimated step size of a range without altering it into a step size object.
33 *
34 * @param {*} min The start point of range.
35 * @param {*} estStepSize The estimated step size.
36 * @return {Object} Return the step size by an object of step x unit.
37 * @return {Number} return.step The step count of units.
38 * @return {Object} return.unit The unit.
39 */
40 // derived from the original numeric segmenter but using 2 instead of 10 as base
41 exactStep: function(min, estStepSize) {
42 let order = Math.floor(Math.log2(estStepSize));
43 let scale = Math.pow(2, order);
44
45 return {
46 unit: {
47 // add one decimal point if estStepSize is not a multiple of scale
48 fixes: -order + (estStepSize % scale === 0 ? 0 : 1),
49 scale: 1,
50 },
51 step: estStepSize,
52 };
53 },
54});
55
0c786d2b
DM
56Ext.define('Proxmox.widget.RRDChart', {
57 extend: 'Ext.chart.CartesianChart',
58 alias: 'widget.proxmoxRRDChart',
59
60 unit: undefined, // bytes, bytespersecond, percent
13fc756d 61
4337ad5b
TL
62 powerOfTwo: false,
63
0c786d2b
DM
64 controller: {
65 xclass: 'Ext.app.ViewController',
66
4337ad5b
TL
67 init: function(view) {
68 this.powerOfTwo = view.powerOfTwo;
69 },
70
0c786d2b 71 convertToUnits: function(value) {
05a977a2
TL
72 let units = ['', 'k', 'M', 'G', 'T', 'P'];
73 let si = 0;
c58d4100
TL
74 let format = '0.##';
75 if (value < 0.1) format += '#';
4337ad5b
TL
76 const baseValue = this.powerOfTwo ? 1024 : 1000;
77 while (value >= baseValue && si < units.length -1) {
78 value = value / baseValue;
0c786d2b
DM
79 si++;
80 }
81
82 // javascript floating point weirdness
83 value = Ext.Number.correctFloat(value);
13fc756d 84
c58d4100
TL
85 // limit decimal points
86 value = Ext.util.Format.number(value, format);
13fc756d 87
4337ad5b
TL
88 let unit = units[si];
89 if (this.powerOfTwo) unit += 'i';
90
91 return `${value.toString()} ${unit}`;
0c786d2b
DM
92 },
93
94 leftAxisRenderer: function(axis, label, layoutContext) {
05a977a2 95 let me = this;
0c786d2b
DM
96 return me.convertToUnits(label);
97 },
98
99 onSeriesTooltipRender: function(tooltip, record, item) {
05a977a2 100 let view = this.getView();
13fc756d 101
05a977a2
TL
102 let suffix = '';
103 if (view.unit === 'percent') {
0c786d2b 104 suffix = '%';
05a977a2 105 } else if (view.unit === 'bytes') {
0c786d2b 106 suffix = 'B';
05a977a2 107 } else if (view.unit === 'bytespersecond') {
0c786d2b
DM
108 suffix = 'B/s';
109 }
13fc756d 110
05a977a2
TL
111 let prefix = item.field;
112 if (view.fieldTitles && view.fieldTitles[view.fields.indexOf(item.field)]) {
113 prefix = view.fieldTitles[view.fields.indexOf(item.field)];
0c786d2b 114 }
efa61051
TL
115 let v = this.convertToUnits(record.get(item.field));
116 let t = new Date(record.get('time'));
117 tooltip.setHtml(`${prefix}: ${v}${suffix}<br>${t}`);
0c786d2b
DM
118 },
119
120 onAfterAnimation: function(chart, eopts) {
efa61051 121 // if the undo button is disabled, disable our tool
05a977a2
TL
122 let ourUndoZoomButton = chart.header.tools[0];
123 let undoButton = chart.interactions[0].getUndoButton();
0c786d2b 124 ourUndoZoomButton.setDisabled(undoButton.isDisabled());
01031528 125 },
0c786d2b 126 },
13fc756d 127
0c786d2b
DM
128 width: 770,
129 height: 300,
130 animation: false,
efa61051
TL
131 interactions: [
132 {
01031528 133 type: 'crosszoom',
efa61051
TL
134 },
135 ],
0c786d2b 136 legend: {
e662b4a0 137 padding: 0,
0c786d2b
DM
138 },
139 listeners: {
01031528 140 animationend: 'onAfterAnimation',
0c786d2b
DM
141 },
142
9531c659
TL
143 constructor: function(config) {
144 let me = this;
145
146 let segmenter = config.powerOfTwo ? 'numericBase2' : 'numeric';
147 config.axes = [
148 {
149 type: 'numeric',
150 position: 'left',
151 grid: true,
152 renderer: 'leftAxisRenderer',
153 minimum: 0,
154 segmenter,
155 },
156 {
157 type: 'time',
158 position: 'bottom',
159 grid: true,
160 fields: ['time'],
161 },
162 ];
163 me.callParent([config]);
164 },
165
0c786d2b 166 initComponent: function() {
05a977a2 167 let me = this;
0c786d2b
DM
168
169 if (!me.store) {
170 throw "cannot work without store";
171 }
172
173 if (!me.fields) {
174 throw "cannot work without fields";
175 }
176
177 me.callParent();
178
179 // add correct label for left axis
05a977a2 180 let axisTitle = "";
0c786d2b
DM
181 if (me.unit === 'percent') {
182 axisTitle = "%";
183 } else if (me.unit === 'bytes') {
184 axisTitle = "Bytes";
185 } else if (me.unit === 'bytespersecond') {
186 axisTitle = "Bytes/s";
51613ace
DC
187 } else if (me.fieldTitles && me.fieldTitles.length === 1) {
188 axisTitle = me.fieldTitles[0];
189 } else if (me.fields.length === 1) {
190 axisTitle = me.fields[0];
0c786d2b
DM
191 }
192
193 me.axes[0].setTitle(axisTitle);
194
e662b4a0 195 me.updateHeader();
0855ba61
TL
196
197 if (me.header && me.legend) {
198 me.header.padding = '4 9 4';
199 me.header.add(me.legend);
200 }
e662b4a0 201
fcb5b70f 202 if (!me.noTool) {
e662b4a0 203 me.addTool({
fcb5b70f
DC
204 type: 'minus',
205 disabled: true,
206 tooltip: gettext('Undo Zoom'),
01031528 207 handler: function() {
05a977a2 208 let undoButton = me.interactions[0].getUndoButton();
fcb5b70f
DC
209 if (undoButton.handler) {
210 undoButton.handler();
211 }
01031528 212 },
e662b4a0 213 });
fcb5b70f
DC
214 }
215
0c786d2b 216 // add a series for each field we get
01031528 217 me.fields.forEach(function(item, index) {
05a977a2 218 let title = item;
0c786d2b
DM
219 if (me.fieldTitles && me.fieldTitles[index]) {
220 title = me.fieldTitles[index];
221 }
9950ec0f
DC
222 me.addSeries(Ext.apply(
223 {
224 type: 'line',
225 xField: 'time',
226 yField: item,
227 title: title,
228 fill: true,
229 style: {
230 lineWidth: 1.5,
01031528 231 opacity: 0.60,
9950ec0f
DC
232 },
233 marker: {
234 opacity: 0,
235 scaling: 0.01,
236 fx: {
237 duration: 200,
01031528
TL
238 easing: 'easeOut',
239 },
9950ec0f
DC
240 },
241 highlightCfg: {
242 opacity: 1,
01031528 243 scaling: 1.5,
9950ec0f
DC
244 },
245 tooltip: {
246 trackMouse: true,
01031528
TL
247 renderer: 'onSeriesTooltipRender',
248 },
0c786d2b 249 },
01031528 250 me.seriesConfig,
9950ec0f 251 ));
0c786d2b
DM
252 });
253
254 // enable animation after the store is loaded
255 me.store.onAfter('load', function() {
256 me.setAnimation(true);
01031528
TL
257 }, this, { single: true });
258 },
0c786d2b 259});