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