1 #include "ScatterCtrl.h"
2 
3 namespace Upp {
4 
DoProcessing()5 void ScatterCtrl::DoProcessing()
6 {
7 	ProcessingDlg dlg;
8 	dlg.Init(*this);
9 	dlg.Run();
10 }
11 
Init(ScatterCtrl & scatter)12 void PropertiesDlg::Init(ScatterCtrl& scatter)
13 {
14 	CtrlLayout(*this, t_("Scatter properties"));
15 	Sizeable().Zoomable();
16 
17 	this->pscatter = &scatter;
18 
19 	tab.Add(measures, t_("Measures"));
20 	tab.Add(texts, 	  t_("Texts"));
21 	tab.Add(legend,   t_("Legend"));
22 	tab.Add(series,   t_("Series"));
23 	tab.Add(general,  t_("General"));
24 	OnTab();
25 
26 	tab.WhenAction = [=]{OnTab();};
27 	butOK.WhenAction = [=] {Close();};
28 }
29 
Set(int itab)30 void PropertiesDlg::Set(int itab)
31 {
32 	tab.Set(itab);
33 	OnTab();
34 }
35 
OnTab()36 void PropertiesDlg::OnTab()
37 {
38 	if (tab.IsAt(measures))
39 		measures.Init(*pscatter);
40 	else if (tab.IsAt(texts))
41 		texts.Init(*pscatter);
42 	else if (tab.IsAt(legend))
43 		legend.Init(*pscatter);
44 	else if (tab.IsAt(series))
45 		series.Init(*pscatter);
46 	else if (tab.IsAt(general))
47 		general.Init(*pscatter);
48 }
49 
Init(ScatterCtrl & scatter)50 void ProcessingDlg::Init(ScatterCtrl& scatter)
51 {
52 	Title(Nvl(scatter.GetTitle(), "Data") + " processing");
53 
54 	Add(splitter.SizePos());
55 	CtrlLayout(list);
56 	CtrlLayout(right);
57 	splitter.Horz(list.SizePos(), right.SizePos());
58 	splitter.SetPos(1500, 0);
59 	Sizeable().Zoomable();
60 
61 	this->pscatter = &scatter;
62 
63 	list.list.Reset();
64 	list.list.SetLineCy(EditField::GetStdHeight());
65 	list.list.AddColumn(t_("Name"));
66 	list.list.AddColumn(t_("Id"));
67 	list.list.ColumnWidths("1 0");
68 	for(int i = 0; i < scatter.GetCount(); i++) {
69 		if (scatter.ScatterDraw::IsVisible(i)) {
70 			list.list.Add(scatter.GetLegend(i), i);
71 			ProcessingTab& tab = tabs.Add();
72 			tab.Init(scatter);
73 			CtrlLayout(tab);
74 			right.rect.Add(tab.SizePos());
75 		}
76 	}
77 	if (list.list.GetCount() > 0)
78 		list.list.SetCursor(0);
79 	list.list.WhenSel = [=] {UpdateFields();};
80 	UpdateFields();
81 
82 	right.butOK.WhenAction = [=] {Close();};
83 }
84 
UpdateFields()85 void ProcessingDlg::UpdateFields()
86 {
87 	int index = list.list.GetCursor();
88 	if (index < 0)
89 		return;
90 
91 	for (int i = 0; i < list.list.GetCount(); ++i)
92 		tabs[i].Hide();
93 	tabs[index].UpdateField(~list.list.Get(0), int(list.list.Get(1)));
94 }
95 
r2Compare(const Vector<Value> & v1,const Vector<Value> & v2)96 int r2Compare(const Vector<Value>& v1, const Vector<Value>& v2) {return double(v1[2]) > double(v2[2]);}
97 
ProcessingTab()98 ProcessingTab::ProcessingTab()
99 {
100 	CtrlLayout(*this);
101 
102 	CtrlLayout(tabFitLeft);
103 	CtrlLayout(tabFitRight);
104 
105 	splitterTabFit.Horz(tabFitLeft.SizePos(), tabFitRightScroll.AddPaneV(tabFitRight).SizePos());
106 	splitterTabFit.SetPos(7000, 0);
107 	CtrlLayout(tabFreqLeft);
108 	CtrlLayout(tabFreqRight);
109 	splitterTabFreq.Horz(tabFreqLeft.SizePos(), tabFreqRightScroll.AddPaneV(tabFreqRight).SizePos());
110 	splitterTabFreq.SetPos(8000, 0);
111 	CtrlLayout(tabOpLeft);
112 	CtrlLayout(tabOpRight);
113 	splitterTabOp.Horz(tabOpLeft.SizePos(), tabOpRight.SizePos());
114 	splitterTabOp.SetPos(8500, 0);
115 	CtrlLayout(tabBestFitLeft);
116 	CtrlLayout(tabBestFitRight);
117 	splitterTabBestFit.Horz(tabBestFitLeft.SizePos(), tabBestFitRight.SizePos());
118 	splitterTabBestFit.SetPos(6000, 0);
119 	CtrlLayout(tabHistLeft);
120 	CtrlLayout(tabHistRight);
121 	splitterTabHist.Horz(tabHistLeft.SizePos(), tabHistRight.SizePos());
122 	splitterTabHist.SetPos(8000, 0);
123 
124 	tab.Add(splitterTabFit.SizePos(),  		t_("Processing"));
125 	tab.Add(splitterTabFreq.SizePos(), 		t_("Frequency"));
126 	tab.Add(splitterTabOp.SizePos(),   		t_("Operations"));
127 	tab.Add(splitterTabBestFit.SizePos(), 	t_("Best fit"));
128 	tab.Add(splitterTabHist.SizePos(), 		t_("Histogram"));
129 	tab.WhenSet = [=] {OnSet();};
130 
131 	tabFreqRight.butFFT.WhenAction = [=] {OnFFT();};
132 	tabFreqRight.opXAxis = 0;
133 	tabFreqRight.opXAxis.WhenAction = [=] {OnFFT();};
134 	tabFreqRight.type.WhenAction = [=] {OnFFT();};
135 	tabFreqRight.type = 0;
136 
137 	for (int i = 0; i < DataSource::GetFFTWindowCount(); ++i)
138 		tabFreqRight.window.Add(InitCaps(DataSource::GetFFTWindowStr(i)));
139 	tabFreqRight.window.SetIndex(1);
140 	tabFreqRight.window.WhenAction = [=] {OnFFT();};
141 	tabFreqRight.num <<= 1;
142 	tabFreqRight.overlapping <<= 0.1;
143 
144 	tabFitRight.opSeries = true;
145 	tabFitRight.opSeries.WhenAction = [=] {OnOp();};
146 	tabFitRight.opAverage.WhenAction = [=] {OnOp();};
147 	tabFitRight.opLinear.WhenAction = [=] {OnOp();};
148 	tabFitRight.opCuadratic.WhenAction = [=] {OnOp();};
149 	tabFitRight.opCubic.WhenAction = [=] {OnOp();};
150 	tabFitRight.opSinus.WhenAction = [=] {OnOp();};
151 	tabFitRight.opSinusTend.WhenAction = [=] {OnOp();};
152 	tabFitRight.opSpline.WhenAction = [=] {OnOp();};
153 	tabFitRight.opDerivative.WhenAction = [=] {OnOp();};
154 	tabFitRight.derOrder.WhenAction = [=] {OnOp();};
155 	tabFitRight.derAccuracy.WhenAction = [=] {OnOp();};
156 	tabFitRight.opSG.WhenAction = [=] {OnOp();};
157 	tabFitRight.sgOrder.WhenAction = [=] {OnOp();};
158 	tabFitRight.sgSize.WhenAction = [=] {OnOp();};
159 	tabFitRight.sgDeg.WhenAction = [=] {OnOp();};
160 	tabFitRight.opMax.WhenAction = [=] {OnOp();};
161 	tabFitRight.opMin.WhenAction = [=] {OnOp();};
162 	tabFitRight.opMovAvg.WhenAction = [=] {OnOp();};
163 	tabFitRight.opSecAvg.WhenAction = [=] {OnOp();};
164 	tabFitRight.opCumAvg.WhenAction = [=] {OnOp();};
165 	tabFitRight.butAutoSensSector.WhenAction = [=] {OnAutoSensSector();};
166 	tabFitRight.width.WhenLostFocus = [=] {OnUpdateSensitivity();};
167 	tabFitRight.width.WhenAction = [=] {OnUpdateSensitivity();};
168 
169 	tabFitRight.opDerivative.Tip(t_("Numerical derivative including derivative order and accuracy (related to window size)"));
170 	tabFitRight.derOrder <<= 1;
171 	tabFitRight.derOrder.Tip(t_("Implemented orders are 1 (first) and 2 (second derivative)"));
172 	tabFitRight.derAccuracy <<= 6;
173 	tabFitRight.derAccuracy.SetInc(2);
174 	tabFitRight.derAccuracy.Tip(t_("Implemented accuracies are 2, 4, 6 and 8"));
175 	tabFitRight.opSG.Tip(t_("Savitzky–Golay filter including derivative order, window size and polynomial degree"));
176 	tabFitRight.sgOrder <<= 0;
177 	tabFitRight.sgOrder.Tip(t_("Implemented orders are 0 (just filter), 1 (first) and 2 (second derivative)"));
178 	tabFitRight.sgSize <<= 5;
179 	//tabFitRight.sgSize.SetInc(2);
180 	tabFitRight.sgSize.Tip(t_("Window size"));
181 	tabFitRight.sgDeg <<= 3;
182 	tabFitRight.sgDeg.Tip(t_("Polynomial degree"));
183 	tabFitRight.numDecimals <<= 3;
184 	tabFitRight.numDecimals.WhenAction = [=] {UpdateEquations();};
185 	tabFitRight.showEquation.WhenAction = [=] {OnShowEquation();};
186 
187 	tabOpRight.xLow.WhenLostFocus = [=] {OnOperation();};
188 	tabOpRight.xHigh.WhenLostFocus = [=] {OnOperation();};
189 
190 	tabBestFitRight.coefficients = 0;
191 	tabBestFitRight.coefficients.Tip(t_("To show real equation coefficients with different precisions or just in text"));
192 	tabBestFitRight.minR2 = 0.6;
193 	tabBestFitRight.minR2.Tip(t_("Min. R2 to plot the equation"));
194 	tabBestFitRight.userFormula <<= "c0 + c1*x^2; c0=0; c1=1";
195 	tabBestFitRight.userFormula.Tip(t_("User suppled equation. Initial guess values separated with ';'"));
196 	tabBestFitRight.gridTrend.AddColumn(t_("Type"), 10);
197 	tabBestFitRight.gridTrend.AddColumn(t_("Equation"), 30);
198 	tabBestFitRight.gridTrend.AddColumn(t_("R2"), 5);
199 	tabBestFitRight.gridTrend.SetLineCy(EditField::GetStdHeight()).MultiSelect();
200 	tabBestFitRight.gridTrend.WhenBar = [=](Bar &menu) {OnArrayBar(menu);};
201 	tabBestFitRight.gridTrend.Sort(r2Compare);
202 	for (int i = 0; i < ExplicitEquation::GetEquationCount(); ++i)
203 		equationTypes.Add(ExplicitEquation::Create(i));
204 	userEquation = new UserEquation;
205 	equationTypes.Add(userEquation);
206 
207 	tabBestFitRight.butFit.Tip(t_("It tries to fit the series with the supported equations"));
208 	tabBestFitRight.butFit.WhenPush = [=] {OnFit();};
209 
210 	tabHistRight.axis.Add(t_("X"));
211 	tabHistRight.axis.Add(t_("Y"));
212 	tabHistRight.axis.SetIndex(1);
213 	tabHistRight.axis.WhenAction = [=] {OnSet();};
214 	tabHistRight.butHist.WhenAction = [=] {OnHist();};
215 	tabHistRight.numVals <<= 30;
216 	tabHistRight.valNormalize <<= 100;
217 	tabHistRight.opStaggered <<= true;
218 
219 	tabHistRight.opNormalize.WhenAction   = [&] {
220 		tabHistRight.valNormalize.Enable(~tabHistRight.opNormalize);
221 		tabHistRight.labNormalize.Enable(~tabHistRight.opNormalize);
222 	};
223 	tabHistRight.opNormalize.WhenAction();
224 
225 	tabFreqFirst = tabOpFirst = tabBestFitFirst = tabHistFirst = true;
226 	avgFirst = linearFirst = cuadraticFirst = cubicFirst = sinusFirst = sinusTendFirst = splineFirst = true;
227 
228 	exclamationOpened = false;
229 	newWidthMax = newWidthMin = newWidthMovAvg-1;
230 	mpm = Null;
231 }
232 
ArrayCopy()233 void ProcessingTab::ArrayCopy() {
234 	tabBestFitRight.gridTrend.SetClipboard(true, true);
235 }
236 
ArraySelect()237 void ProcessingTab::ArraySelect() {
238 	tabBestFitRight.gridTrend.Select(0, tabBestFitRight.gridTrend.GetCount(), true);
239 }
240 
OnArrayBar(Bar & menu)241 void ProcessingTab::OnArrayBar(Bar &menu) {
242 	menu.Add(t_("Select all"), Null, [=] {ArraySelect();}).Key(K_CTRL_A).Help(t_("Select all rows"));
243 	menu.Add(t_("Copy"), ScatterImgP::Copy(), [=] {ArrayCopy();}).Key(K_CTRL_C).Help(t_("Copy selected rows"));
244 }
245 
OnFit()246 void ProcessingTab::OnFit() {
247 	WaitCursor waitcursor;
248 
249 	if (pscatter->IsDeletedDataSource(id))
250 		return;
251 	DataSource &ds = pscatter->GetDataSource(id);
252 
253 	userEquation->Init("User", ~tabBestFitRight.userFormula, "x");
254 
255 	Array<double> r2;
256 	r2.SetCount(equationTypes.GetCount());
257 
258 	for (int i = 0; i < equationTypes.GetCount(); ++i) {
259 		equationTypes[i].GuessCoeff(ds);
260 		equationTypes[i].Fit(ds, r2[i]);
261 	}
262 	tabBestFitLeft.scatter.RemoveAllSeries();
263 	tabBestFitLeft.scatter.AddSeries(ds).Legend("Series").NoMark();
264 	for (int i = 0; i < equationTypes.GetCount(); ++i) {
265 		if (r2[i] >= tabBestFitRight.minR2)
266 			tabBestFitLeft.scatter.AddSeries(equationTypes[i]).Legend(equationTypes[i].GetFullName()).NoMark().Stroke(2);
267 	}
268 	tabBestFitLeft.scatter.ZoomToFit(true, true);
269 
270 	int numDecimals = 3;
271 	switch (tabBestFitRight.coefficients) {
272 	case 1:	numDecimals = 40;	break;
273 	case 2:	numDecimals = Null;	break;
274 	}
275 	tabBestFitRight.gridTrend.Clear();
276 	for (int i = 0; i < equationTypes.GetCount(); ++i)
277 		tabBestFitRight.gridTrend.Add(equationTypes[i].GetFullName(), equationTypes[i].GetEquation(numDecimals), r2[i]);
278 	tabBestFitRight.gridTrend.SetSortColumn(2, true);
279 }
280 
OnOp()281 void ProcessingTab::OnOp()
282 {
283 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
284 		return;
285 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
286 
287 	if (data.IsParam() || data.IsExplicit())
288 		return;
289 
290 	if (~tabFitRight.opAverage && avgFirst) {
291 		double r2;
292 		average.Fit(data, r2);
293 		avgFirst = false;
294 	}
295 	if (~tabFitRight.opLinear && linearFirst) {
296 		if (linear.Fit(data, r2Linear) < 0) {
297 			tabFitRight.opLinear <<= false;
298 			tabFitRight.opLinear.Enable(false);
299 		} else
300 			linearFirst = false;
301 	}
302 	if (~tabFitRight.opCuadratic && cuadraticFirst) {
303 		cuadratic.GuessCoeff(data);
304 		if (cuadratic.Fit(data, r2Cuadratic) < 0) {
305 			tabFitRight.opCuadratic <<= false;
306 			tabFitRight.opCuadratic.Enable(false);
307 		} else
308 			cuadraticFirst = false;
309 	}
310 	if (~tabFitRight.opCubic && cubicFirst) {
311 		cubic.GuessCoeff(data);
312 		if (cubic.Fit(data, r2Cubic) < 0) {
313 			tabFitRight.opCubic <<= false;
314 			tabFitRight.opCubic.Enable(false);
315 		} else
316 			cubicFirst = false;
317 	}
318 	if (~tabFitRight.opSinus && sinusFirst) {
319 		sinus.GuessCoeff(data);
320 		if (sinus.Fit(data, r2Sinus) < 0) {
321 			tabFitRight.opSinus <<= false;
322 			tabFitRight.opSinus.Enable(false);
323 		} else
324 			sinusFirst = false;
325 	}
326 	if (~tabFitRight.opSinusTend && sinusTendFirst) {
327 		DataXRange dataXRange;
328 		dataXRange.Init(data, Null, Null);
329 		double r2SinusTendBest = Null;
330 		SinEquation sinusTendBest;
331 		for (int iLow = 9; iLow >= 0; iLow--) {
332 			double xLow = data.x(int64(data.GetCount()*iLow/10.));
333 			dataXRange.SetXLow(xLow);
334 			sinusTend.GuessCoeff(dataXRange);
335 			if (sinusTend.Fit(dataXRange, r2SinusTend) < 0)
336 				break;
337 			if (!IsNull(r2SinusTendBest) && r2SinusTendBest > r2SinusTend)
338 				break;
339 			r2SinusTendBest = r2SinusTend;
340 			sinusTendBest = sinusTend;
341 		}
342 		if (IsNull(r2SinusTendBest)) {
343 			tabFitRight.opSinusTend <<= false;
344 			tabFitRight.opSinusTend.Enable(false);
345 		} else {
346 			splineFirst = false;
347 			r2SinusTend = r2SinusTendBest;
348 			sinusTend = sinusTendBest;
349 		}
350 	}
351 	if (~tabFitRight.opSpline && splineFirst) {
352 		if (spline.Fit(data) < 0) {
353 			tabFitRight.opSpline <<= false;
354 			tabFitRight.opSpline.Enable(false);
355 		} else
356 			splineFirst = false;
357 	}
358 	OnUpdateSensitivity();
359 
360 	tabFitLeft.scatter.ScatterDraw::Show(0, tabFitRight.opSeries);
361 	tabFitLeft.scatter.ScatterDraw::Show(1, tabFitRight.opAverage);
362 	tabFitLeft.scatter.ScatterDraw::Show(2, tabFitRight.opLinear);
363 	tabFitLeft.scatter.ScatterDraw::Show(3, tabFitRight.opCuadratic);
364 	tabFitLeft.scatter.ScatterDraw::Show(4, tabFitRight.opCubic);
365 	tabFitLeft.scatter.ScatterDraw::Show(5, tabFitRight.opSinus);
366 	tabFitLeft.scatter.ScatterDraw::Show(6, tabFitRight.opSinusTend);
367 	tabFitLeft.scatter.ScatterDraw::Show(7, tabFitRight.opSpline);
368 	tabFitLeft.scatter.ScatterDraw::Show(8, tabFitRight.opDerivative);
369 	tabFitLeft.scatter.ScatterDraw::Show(9, tabFitRight.opSG);
370 	tabFitLeft.scatter.ScatterDraw::Show(10,tabFitRight.opMax);
371 	tabFitLeft.scatter.ScatterDraw::Show(11,tabFitRight.opMin);
372 	tabFitLeft.scatter.ScatterDraw::Show(12,tabFitRight.opMovAvg);
373 	tabFitLeft.scatter.ScatterDraw::Show(13,tabFitRight.opSecAvg);
374 	tabFitLeft.scatter.ScatterDraw::Show(14,tabFitRight.opCumAvg);
375 
376 	UpdateEquations();
377 	OnShowEquation();
378 }
379 
OnAutoSensSector()380 void ProcessingTab::OnAutoSensSector()
381 {
382 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
383 		return;
384 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
385 	Vector<Pointf> secAvg;
386 	double baseWidth;
387 
388 	baseWidth = 0;
389 	for (int64 i = 1; i < data.GetCount(); ++i)
390 		baseWidth += (data.x(i) - data.x(i-1));
391 	baseWidth /= (data.GetCount() - 1);
392 
393 	double rangeX = data.x(data.GetCount() - 1) - data.x(int64(0));
394 
395 	for(double width = baseWidth; width < rangeX/10.; width += baseWidth) {
396 		secAvg = data.SectorAverageY(width);
397 		VectorPointf sector(secAvg);
398 		Vector<int64> ids;
399 		sector.MaxListY(ids, 10*baseWidth);
400 		if (ids.GetCount() < 5) {
401 			tabFitRight.width <<= width;
402 			return;
403 		}
404 	}
405 	tabFitLeft.scatter.Refresh();
406 }
407 
OnOperation()408 void ProcessingTab::OnOperation()
409 {
410 	if (exclamationOpened)	// To avoid WhenLostFocus to be called when Exclamation() is opened
411 		return;
412 	exclamationOpened = true;
413 	if (!IsNull(tabOpRight.xLow) && !IsNull(tabOpRight.xHigh)) {
414 		if (tabOpRight.xLow >= tabOpRight.xHigh) {
415 			Exclamation(t_("'x >' has to be lower than 'x <'"));
416 			exclamationOpened = false;
417 			return;
418 		}
419 	}
420 	exclamationOpened = false;
421 	if (pscatter->IsDeletedDataSource(id))
422 		return;
423 	dataXRange.Init(pscatter->GetDataSource(id), tabOpRight.xLow, tabOpRight.xHigh);
424 	tabOpLeft.scatter.Refresh();
425 }
426 
UpdateField(const String _name,int _id)427 void ProcessingTab::UpdateField(const String _name, int _id)
428 {
429 	id = _id;
430 	name.SetText(_name);
431 
432 	tabFitLeft.scatter.RemoveAllSeries();
433 	if (pscatter->IsDeletedDataSource(id))
434 		return;
435 	tabFitLeft.scatter.AddSeries(pscatter->GetDataSource(id)).SetSequentialX(pscatter->GetSequentialX())
436 				   .Legend(pscatter->GetLegend(id));
437 	tabFitLeft.scatter.SetFastViewX(pscatter->GetFastViewX());
438 
439 	tabFitLeft.scatter.SetFillColor(0, pscatter->GetFillColor(id));
440 	tabFitLeft.scatter.Dash(0, pscatter->GetDash(id));
441 
442 	Upp::Color color;
443 	double thickness;
444 	pscatter->GetStroke(0, thickness, color);
445 	tabFitLeft.scatter.Stroke(0, thickness, color);
446 	tabFitLeft.scatter.MarkStyle(0, pscatter->GetMarkStyleName(id));
447 	tabFitLeft.scatter.SetMarkColor(0, pscatter->GetMarkColor(id));
448 	tabFitLeft.scatter.SetMarkWidth(0, pscatter->GetMarkWidth(id));
449 	tabFitLeft.scatter.MarkStyle(0, pscatter->GetMarkStyleName(id));
450 	tabFitLeft.scatter.SetLegendAnchor(ScatterDraw::RIGHT_TOP).SetLegendFillColor(Null);
451 
452 	tabFitLeft.scatter.Units(0, pscatter->GetUnitsY(id), pscatter->GetUnitsX(id));
453 	tabFitLeft.scatter.SetLabelX(pscatter->GetLabelX());
454 
455 	bool primary = pscatter->IsDataPrimaryY(id);
456     tabFitLeft.scatter.SetRange(pscatter->GetXRange(), primary ? pscatter->GetYRange() : pscatter->GetY2Range());
457 	tabFitLeft.scatter.SetMajorUnits(pscatter->GetMajorUnitsX(), primary ? pscatter->GetMajorUnitsY() : pscatter->GetMajorUnitsY2());
458 	tabFitLeft.scatter.SetXYMin(pscatter->GetXMin(), primary ? pscatter->GetYMin() : pscatter->GetY2Min());
459 
460 	tabFitLeft.scatter.ShowInfo().ShowContextMenu().ShowProcessDlg().ShowPropertiesDlg().SetMouseHandlingLinked(true, true);
461 
462 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
463 		return;
464 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
465 
466 	if (!data.IsParam()/* && !data.IsExplicit()*/) {
467 		double avg = data.AvgY();
468 		tabFitRight.eAverage = avg;
469 		tabFitRight.eRMS = data.RMSY();
470 		tabFitRight.eStdDev = data.StdDevY(avg);
471 		int64 idmx;
472 		double val;
473 		val = data.MaxY(idmx);
474 		if (!IsNull(val)) {
475 			tabFitRight.eMax <<= Format("(%f,%f)", data.x(idmx), val);
476 			Pointf p = data.MaxSubDataImpY(idmx, 3);
477 			if (!IsNull(p))
478 				tabFitRight.eMaxImp = Format("(%f,%f)", p.x, p.y);
479 			val = data.MinY(idmx);
480 			if (!IsNull(val))
481 				tabFitRight.eMin = Format("(%f,%f)", data.x(idmx), val);
482 		}
483 	}
484 	if (!data.IsParam() && !data.IsExplicit()) {
485 		tabFitRight.width <<= pscatter->GetXRange()/15.;
486 		tabFitRight.width.SetInc(pscatter->GetXRange()/15./2.);
487 
488 		tabFitLeft.scatter.AddSeries(average).NoMark().Stroke(1.5);
489 		tabFitLeft.scatter.AddSeries(linear).NoMark().Stroke(1.5);
490 		tabFitLeft.scatter.AddSeries(cuadratic).NoMark().Stroke(1.5);
491 		tabFitLeft.scatter.AddSeries(cubic).NoMark().Stroke(1.5);
492 		tabFitLeft.scatter.AddSeries(sinus).NoMark().Stroke(1.5);
493 		tabFitLeft.scatter.AddSeries(sinusTend).NoMark().Stroke(1.5);
494 		tabFitLeft.scatter.AddSeries(spline).NoMark().Dash(LINE_SOLID).Stroke(1.5);
495 		tabFitLeft.scatter.AddSeries(derivative).NoMark().Dash(LINE_SOLID).Stroke(1.5);
496 		tabFitLeft.scatter.AddSeries(sg).NoMark().Dash(LINE_SOLID).Stroke(1.5);
497 		tabFitLeft.scatter.AddSeries(upperEnvelope).Legend(pscatter->GetLegend(id) + String("-") + t_("Max"))
498 						.NoMark().Dash(LINE_DASHED).Stroke(1.5).SetSequentialX(true);
499 		tabFitLeft.scatter.AddSeries(lowerEnvelope).Legend(pscatter->GetLegend(id) + String("-") + t_("Min"))
500 						.NoMark().Dash(LINE_DASHED).SetSequentialX(true);
501 		tabFitLeft.scatter.AddSeries(movAvg).Stroke(1.5).Legend(pscatter->GetLegend(id) + String("-") + t_("MovAvg")).NoMark();
502 		tabFitLeft.scatter.AddSeries(secAvg).Stroke(1.5).Legend(pscatter->GetLegend(id) + String("-") + t_("SecAvg")).NoMark();
503 		tabFitLeft.scatter.AddSeries(cumAvg).Stroke(1.5).Legend(pscatter->GetLegend(id) + String("-") + t_("CumAvg")).NoMark();
504 
505 		OnOp();
506 	} else {
507 		tabFitRight.opSeries.Enable(false);
508 		tabFitRight.opAverage.Enable(false);
509 		tabFitRight.opLinear.Enable(false);
510 		tabFitRight.opCuadratic.Enable(false);
511 		tabFitRight.opCubic.Enable(false);
512 		tabFitRight.opSinus.Enable(false);
513 		tabFitRight.opSinusTend.Enable(false);
514 		tabFitRight.opSpline.Enable(false);
515 		tabFitRight.opDerivative.Enable(false);
516 		tabFitRight.derOrder.Enable(false);
517 		tabFitRight.derAccuracy.Enable(false);
518 		tabFitRight.opSG.Enable(false);
519 		tabFitRight.sgOrder.Enable(false);
520 		tabFitRight.opMax.Enable(false);
521 		tabFitRight.opMin.Enable(false);
522 		tabFitRight.opMovAvg.Enable(false);
523 		tabFitRight.opSecAvg.Enable(false);
524 		tabFitRight.opCumAvg.Enable(false);
525 	}
526 
527 	Show();
528 }
529 
OnUpdateSensitivity()530 void ProcessingTab::OnUpdateSensitivity()
531 {
532 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
533 		return;
534 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
535 
536 	bool refresh = false;
537 	if (tabFitRight.opDerivative) {
538 		bool isOdd = int(~(tabFitRight.derAccuracy))%2;
539 		if (IsNull(tabFitRight.derAccuracy) || isOdd)
540 			derivative.Clear();
541 		else
542 			derivative = data.DerivativeY(~tabFitRight.derOrder, ~tabFitRight.derAccuracy);
543 
544 		refresh = true;
545 	}
546 	if (tabFitRight.opSG) {
547 		int side = int(~tabFitRight.sgSize)/2;
548 		if (!SavitzkyGolay_CheckParams(side, side, ~tabFitRight.sgDeg, ~tabFitRight.sgOrder))
549 			sg.Clear();
550 		else
551 			sg = data.SavitzkyGolayY(~tabFitRight.sgDeg, ~tabFitRight.sgSize, ~tabFitRight.sgOrder);
552 
553 		refresh = true;
554 	}
555 	if (tabFitRight.opMax && newWidthMax != tabFitRight.width) {
556 		newWidthMax = tabFitRight.width;
557 
558 		upperEnvelope.Clear();
559 		Vector<int64> idsUpper = data.UpperEnvelopeY(tabFitRight.width);
560 		mpm = data.StdDevY()*sqrt(2*log(idsUpper.GetCount()));
561 		for (int i = 0; i < idsUpper.GetCount(); ++i)
562 			upperEnvelope << Pointf(data.x(idsUpper[i]), data.y(idsUpper[i]));
563 		refresh = true;
564 	}
565 	tabFitRight.labNumMax.Enable(tabFitRight.opMax);
566 	tabFitRight.numMax.Enable(tabFitRight.opMax);
567 	tabFitRight.numMax <<= (tabFitRight.opMax ? upperEnvelope.GetCount() : Null);
568 	tabFitRight.labMPM.Enable(tabFitRight.opMax);
569 	tabFitRight.eMPM.Enable(tabFitRight.opMax);
570 	tabFitRight.eMPM <<= (tabFitRight.opMax ? mpm : Null);
571 	if (tabFitRight.opMin && newWidthMin != tabFitRight.width) {
572 		newWidthMin = tabFitRight.width;
573 
574 		lowerEnvelope.Clear();
575 		Vector<int64> idsLower = data.LowerEnvelopeY(tabFitRight.width);
576 		for (int i = 0; i < idsLower.GetCount(); ++i)
577 			lowerEnvelope << Pointf(data.x(idsLower[i]), data.y(idsLower[i]));
578 		refresh = true;
579 	}
580 	tabFitRight.labNumMin.Enable(tabFitRight.opMin);
581 	tabFitRight.numMin.Enable(tabFitRight.opMin);
582 	tabFitRight.numMin <<= (tabFitRight.opMin ? lowerEnvelope.GetCount() : Null);
583 	if (tabFitRight.opMovAvg && newWidthMovAvg != tabFitRight.width) {
584 		newWidthMovAvg = tabFitRight.width;
585 
586 		movAvg = data.MovingAverageY(tabFitRight.width);
587 		refresh = true;
588 	}
589 	if (tabFitRight.opSecAvg && newWidthMovAvg != tabFitRight.width) {
590 		newWidthMovAvg = tabFitRight.width;
591 
592 		secAvg = data.SectorAverageY(tabFitRight.width);
593 		refresh = true;
594 	}
595 	if (tabFitRight.opCumAvg) {
596 		cumAvg = data.CumulativeAverageY();
597 		refresh = true;
598 	}
599 
600 	if (refresh)
601 		tabFitLeft.scatter.Refresh();
602 }
603 
OnSet()604 void ProcessingTab::OnSet()
605 {
606 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
607 		return;
608 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
609 
610 	if (tabFreqFirst && tab.IsAt(splitterTabFreq)) {
611 		tabFreqFirst = false;
612 		if (data.IsParam() || data.IsExplicit()) {
613 			tabFreqLeft.comments.SetText(t_("Impossible to calculate FFT from a not sampled series"));
614 			tabFreqRight.butFFT.Enable(false);
615 		} else if (data.GetCount() < 5) {
616 			tabFreqLeft.comments.SetText(t_("Insufficient data to calculate FFT"));
617 			tabFreqRight.butFFT.Enable(false);
618 		} else {
619 			double mindT, maxdT = Null;
620 			mindT = -maxdT;
621 			for (int64 i = 1; i < data.GetCount(); ++i) {
622 				double d2 = data.x(i), d1 = data.x(i - 1);
623 
624 				if (!IsNull(d1) && !IsNull(d2)) {
625 					double delta = (d2 - d1);
626 					mindT = min(delta, mindT);
627 					maxdT = max(delta, maxdT);
628 				}
629 			}
630 			if ((maxdT - mindT)/maxdT > 0.00001)
631 				tabFreqLeft.comments.SetText(Format(t_("Sampling time changes from %f to %f"), mindT, maxdT));
632 			else
633 				tabFreqLeft.comments.SetText("");
634 			tabFreqRight.samplingTime = (maxdT + mindT)/2.;
635 		}
636 	} else if (tabOpFirst && tab.IsAt(splitterTabOp)) {
637 		if (pscatter->IsDeletedDataSource(id))
638 			return;
639 
640 		tabOpFirst = false;
641 
642 		tabOpLeft.scatter.RemoveAllSeries();
643 		String legend = pscatter->GetLegend(id);
644 		double xLow = pscatter->GetDataSource(id).MinX();
645 		if (IsNull(xLow))
646 			xLow = pscatter->GetXMin();
647 		tabOpRight.xLow <<= xLow;
648 		double xHigh = pscatter->GetDataSource(id).MaxX();
649 		if (IsNull(xHigh))
650 			xHigh = pscatter->GetXMin() + pscatter->GetXRange();
651 		tabOpRight.xHigh <<= xHigh;
652 		dataXRange.Init(pscatter->GetDataSource(id), xLow, xHigh);
653 		tabOpLeft.scatter.AddSeries(dataXRange).SetSequentialX(pscatter->GetSequentialX())
654 					   .Legend(legend + String("-") + t_("Processed")).NoMark()
655 					   .Stroke(8, Upp::Color(115, 214, 74));
656 		tabOpLeft.scatter.AddSeries(pscatter->GetDataSource(id)).SetSequentialX(pscatter->GetSequentialX())
657 					   .Legend(legend).NoMark().Stroke(2, Blue());
658 		tabOpLeft.scatter.SetFastViewX(pscatter->GetFastViewX());
659 
660 		tabOpLeft.scatter.SetLegendAnchor(ScatterDraw::RIGHT_TOP).SetLegendFillColor(Null);
661 
662 		tabOpLeft.scatter.Units(0, pscatter->GetUnitsY(id), pscatter->GetUnitsX(id));
663 		tabOpLeft.scatter.SetLabelX(pscatter->GetLabelX());
664 
665 		bool primary = pscatter->IsDataPrimaryY(id);
666 		tabOpLeft.scatter.SetRange(pscatter->GetXRange(), primary ? pscatter->GetYRange() : pscatter->GetY2Range());
667 		tabOpLeft.scatter.SetMajorUnits(pscatter->GetMajorUnitsX(), primary ? pscatter->GetMajorUnitsY() : pscatter->GetMajorUnitsY2());
668 		tabOpLeft.scatter.SetXYMin(pscatter->GetXMin(), primary ? pscatter->GetYMin() : pscatter->GetY2Min());
669 
670 		tabOpLeft.scatter.ShowInfo().ShowContextMenu().ShowProcessDlg().ShowPropertiesDlg().SetMouseHandlingLinked(true, true);
671 	} else if (tabBestFitFirst && tab.IsAt(splitterTabBestFit)) {
672 		if (pscatter->IsDeletedDataSource(id))
673 			return;
674 
675 		tabBestFitFirst = false;
676 
677 		tabBestFitLeft.scatter.RemoveAllSeries();
678 		String legend = pscatter->GetLegend(id);
679 
680 		tabBestFitLeft.scatter.AddSeries(pscatter->GetDataSource(id)).SetSequentialX(pscatter->GetSequentialX())
681 					   .Legend(legend).NoMark().Stroke(2);
682 		tabBestFitLeft.scatter.SetFastViewX(pscatter->GetFastViewX());
683 
684 		tabBestFitLeft.scatter.SetLegendAnchor(ScatterDraw::RIGHT_TOP).SetLegendFillColor(Null);
685 
686 		tabBestFitLeft.scatter.Units(0, pscatter->GetUnitsY(id), pscatter->GetUnitsX(id));
687 		tabBestFitLeft.scatter.SetLabelX(pscatter->GetLabelX());
688 
689 		bool primary = pscatter->IsDataPrimaryY(id);
690 		tabBestFitLeft.scatter.SetRange(pscatter->GetXRange(), primary ? pscatter->GetYRange() : pscatter->GetY2Range());
691 		tabBestFitLeft.scatter.SetMajorUnits(pscatter->GetMajorUnitsX(), primary ? pscatter->GetMajorUnitsY() : pscatter->GetMajorUnitsY2());
692 		tabBestFitLeft.scatter.SetXYMin(pscatter->GetXMin(), primary ? pscatter->GetYMin() : pscatter->GetY2Min());
693 
694 		tabBestFitLeft.scatter.ShowInfo().ShowContextMenu().ShowProcessDlg().ShowPropertiesDlg().SetMouseHandlingLinked(true, true);
695 	} else if (tab.IsAt(splitterTabHist)) {
696 		//tabHistFirst = false;
697 		if (data.IsParam() || data.IsExplicit()) {
698 			tabHistLeft.comments.SetText(t_("Impossible to calculate histogram from a not sampled series"));
699 			tabHistRight.butHist.Enable(false);
700 		} else if (data.GetCount() < 5) {
701 			tabHistLeft.comments.SetText(t_("Insufficient data to calculate histogram"));
702 			tabHistRight.butHist.Enable(false);
703 		} else {
704 			if (~tabHistRight.axis == t_("Y")) {
705 				tabHistRight.minVal <<= data.MinY();
706 				tabHistRight.maxVal <<= data.MaxY();
707 			} else {
708 				tabHistRight.minVal <<= data.MinX();
709 				tabHistRight.maxVal <<= data.MaxX();
710 			}
711 		}
712 	}
713 }
714 
UpdateEquations()715 void ProcessingTab::UpdateEquations()
716 {
717 	tabFitRight.eqAverage = tabFitRight.opAverage ? average.GetEquation(tabFitRight.numDecimals) : "";
718 	tabFitRight.eqLinear = tabFitRight.opLinear ? linear.GetEquation(tabFitRight.numDecimals) : "";
719 	tabFitRight.r2Linear = tabFitRight.opLinear ? r2Linear : Null;
720 	tabFitRight.eqCuadratic = tabFitRight.opCuadratic ? cuadratic.GetEquation(tabFitRight.numDecimals) : "";
721 	tabFitRight.r2Cuadratic = tabFitRight.opCuadratic ? r2Cuadratic : Null;
722 	tabFitRight.eqCubic = tabFitRight.opCubic ? cubic.GetEquation(tabFitRight.numDecimals) : "";
723 	tabFitRight.r2Cubic = tabFitRight.opCubic ? r2Cubic : Null;
724 	tabFitRight.eqSinus = tabFitRight.opSinus ? sinus.GetEquation(tabFitRight.numDecimals) : "";
725 	tabFitRight.r2Sinus = tabFitRight.opSinus ? r2Sinus : Null;
726 	tabFitRight.eqSinusTend = tabFitRight.opSinusTend ? sinusTend.GetEquation(tabFitRight.numDecimals) : "";
727 	tabFitRight.r2SinusTend = tabFitRight.opSinusTend ? r2SinusTend : Null;
728 }
729 
OnShowEquation()730 void ProcessingTab::OnShowEquation()
731 {
732 	bool show = tabFitRight.showEquation;
733 	tabFitLeft.scatter.Legend(1, pscatter->GetLegend(id) + String("-") +
734 						(show && tabFitRight.opAverage ? average.GetEquation(tabFitRight.numDecimals) : String(t_("Average"))));
735 	tabFitLeft.scatter.Legend(2, pscatter->GetLegend(id) + String("-") +
736 						(show && tabFitRight.opLinear ? linear.GetEquation(tabFitRight.numDecimals) : String(t_("Linear"))));
737 	tabFitLeft.scatter.Legend(3, pscatter->GetLegend(id) + String("-") +
738 						(show && tabFitRight.opCuadratic ? cuadratic.GetEquation(tabFitRight.numDecimals) : String(t_("Cuadratic"))));
739 	tabFitLeft.scatter.Legend(4, pscatter->GetLegend(id) + String("-") +
740 						(show && tabFitRight.opCubic ? cubic.GetEquation(tabFitRight.numDecimals) : String(t_("Cubic"))));
741 	tabFitLeft.scatter.Legend(5, pscatter->GetLegend(id) + String("-") +
742 						(show && tabFitRight.opSinus ? sinus.GetEquation(tabFitRight.numDecimals) : String(t_("Sinusoidal"))));
743 	tabFitLeft.scatter.Legend(6, pscatter->GetLegend(id) + String("-") +
744 						(show && tabFitRight.opSinusTend ? sinusTend.GetEquation(tabFitRight.numDecimals) : String(t_("Sinusoidal tend"))));
745 	tabFitLeft.scatter.Legend(7, pscatter->GetLegend(id) + String("-") + String(t_("Spline")));
746 	tabFitLeft.scatter.Legend(8, pscatter->GetLegend(id) + String("-") + String(Format(t_("Der_%d"), ~tabFitRight.derOrder)));
747 	tabFitLeft.scatter.Legend(9, pscatter->GetLegend(id) + String("-") + String(Format(t_("S_G_%d"), ~tabFitRight.sgOrder)));
748 	tabFitLeft.scatter.Refresh();
749 }
750 
OnFFT()751 void ProcessingTab::OnFFT()
752 {
753 	String errText;
754 	tabFreqLeft.scatter.RemoveAllSeries();
755 	double samplingTime = tabFreqRight.samplingTime;
756 	if (samplingTime < 0) {
757 		Exclamation(t_("Incorrect sampling time"));
758 		return;
759 	}
760 	int64 idMaxFFT;
761 	{
762 		WaitCursor waitcursor;
763 
764 		if (tabFitLeft.scatter.IsDeletedDataSource(0))
765 			return;
766 		DataSource &data = tabFitLeft.scatter.GetDataSource(0);
767 
768 		Vector<Pointf> orderedSeries;
769 		for (int64 i = 0; i < data.GetCount(); ++i) {		// Clean Nulls
770 			if (!IsNull(data.x(i)) && !IsNull(data.y(i)))
771 				orderedSeries << Pointf(data.x(i), data.y(i));
772 		}
773 		//if (orderedSeries.GetCount() != data.GetCount())
774 		//	errText << Format(t_("Removed %d Null points."), data.GetCount() - orderedSeries.GetCount());
775 
776 		PointfLess less;
777 		Sort(orderedSeries, less);
778 
779 		Vector<double> resampledSeries;
780 		resampledSeries << orderedSeries[0].y;
781 		double nextSample = orderedSeries[0].x + samplingTime;
782 		for (int i = 0; i < orderedSeries.GetCount() - 1;) {
783 			if (orderedSeries[i].x == nextSample) {
784 				resampledSeries << orderedSeries[i].y;
785 				nextSample += samplingTime;
786 			} else if (orderedSeries[i].x < nextSample && orderedSeries[i + 1].x > nextSample) {	// Linear interpolation
787 				resampledSeries << (orderedSeries[i].y + (orderedSeries[i + 1].y - orderedSeries[i].y)*
788 								   (nextSample - orderedSeries[i].x)/(orderedSeries[i + 1].x - orderedSeries[i].x));
789 				nextSample += samplingTime;
790 			} else
791 				++i;
792 		}
793 		if (orderedSeries[orderedSeries.GetCount() - 1].x == nextSample)
794 			resampledSeries << orderedSeries[orderedSeries.GetCount() - 1].y;
795 
796 		VectorY<double> series(resampledSeries, 0, samplingTime);
797 		fft = series.FFTY(samplingTime, tabFreqRight.opXAxis == 1, tabFreqRight.type,
798 							tabFreqRight.window.GetIndex(), tabFreqRight.num, tabFreqRight.overlapping);
799 		VectorPointf fftData(fft);
800 		double mxy = fftData.MaxY(idMaxFFT);
801 		if (!IsNull(mxy))
802 			tabFreqRight.eMax <<= Format("(%f,%f)", fftData.x(idMaxFFT), mxy);
803 
804 		if (tabFreqRight.type == DataSource::T_PSD) {
805 			double m_1, m0, m1, m2;
806 			fftData.GetSpectralMomentsY(tabFreqRight.opXAxis == 1, m_1, m0, m1, m2);
807 			tabFreqRight.m_1 <<= FormatDouble(m_1);
808 			tabFreqRight.m0  <<= FormatDouble(m0);
809 			tabFreqRight.m1  <<= FormatDouble(m1);
810 			tabFreqRight.m2  <<= FormatDouble(m2);
811 		} else {
812 			tabFreqRight.m_1 <<= "";
813 			tabFreqRight.m0  <<= "";
814 			tabFreqRight.m1  <<= "";
815 			tabFreqRight.m2  <<= "";
816 		}
817 		tabFreqRight.m_1.Enable(tabFreqRight.type == DataSource::T_PSD);
818 		tabFreqRight.m0.Enable(tabFreqRight.type == DataSource::T_PSD);
819 		tabFreqRight.m1.Enable(tabFreqRight.type == DataSource::T_PSD);
820 		tabFreqRight.m2.Enable(tabFreqRight.type == DataSource::T_PSD);
821 		tabFreqRight.labSpectral.Enable(tabFreqRight.type == DataSource::T_PSD);
822 	}
823 	if (fft.IsEmpty()) {
824 		tabFreqLeft.comments.SetText(errText);
825 		Exclamation(t_("Error obtaining FFT"));
826 		return;
827 	}
828 
829 	String strtype;
830 	switch(tabFreqRight.type) {
831 	case DataSource::T_FFT:		strtype = t_("FFT");					break;
832 	case DataSource::T_PHASE:	strtype = t_("FFT-phase [rad]");		break;
833 	case DataSource::T_PSD:		strtype = t_("Power Spectral Density");	break;
834 	}
835 	String legend = tabFitLeft.scatter.GetLegend(0) + String("-") + strtype;
836 	tabFreqLeft.scatter.AddSeries(fft).Legend(legend);
837 	tabFreqLeft.scatter.ShowInfo().ShowContextMenu().ShowProcessDlg().ShowPropertiesDlg().SetMouseHandlingLinked(true, true);
838 	tabFreqLeft.scatter.SetLabelX(tabFreqRight.opXAxis == 1 ? t_("Frequency [Hz]") : t_("Period [sec]"));
839 	tabFreqLeft.scatter.SetLabelY(legend);
840 	tabFreqLeft.scatter.ZoomToFit(true, true);
841 	if (idMaxFFT > 0 && fft[int(idMaxFFT)].x < (fft[fft.GetCount() - 1].x)/2)
842 		tabFreqLeft.scatter.SetRange(fft[int(idMaxFFT)].x*2, Null);
843 
844 	tabFreqLeft.comments.SetText(errText);
845 }
846 
OnHist()847 void ProcessingTab::OnHist() {
848 	tabHistLeft.scatter.RemoveAllSeries();
849 
850 	if (tabFitLeft.scatter.IsDeletedDataSource(0))
851 		return;
852 	DataSource &data = tabFitLeft.scatter.GetDataSource(0);
853 
854 	double minVal = ~tabHistRight.minVal;
855 	double maxVal = ~tabHistRight.maxVal;
856 	if (minVal >= maxVal) {
857 		Exclamation(Format(t_("Min val %d has to be lower than Max val %f"), minVal, maxVal));
858 		return;
859 	}
860 	int numVals = ~tabHistRight.numVals;
861 	bool normalize = ~tabHistRight.opNormalize;
862 	double valNormalize = ~tabHistRight.valNormalize;
863 	bool isY = ~tabHistRight.axis == t_("Y");
864 
865 	histogram.Create(data, minVal, maxVal, numVals, isY).Accumulative(~tabHistRight.opAccumulative);
866 
867 	if (normalize)
868 		histogram.Normalize(valNormalize);
869 
870 	String legend = tabFitLeft.scatter.GetLegend(0) + String("-") + t_("Histogram");
871 	tabHistLeft.scatter.AddSeries(histogram).Legend(legend).NoMark().
872 		Units(normalize ? "x" + FormatDouble(valNormalize) : "#", isY ?
873 											tabFitLeft.scatter.GetUnitsY(0) :
874 											tabFitLeft.scatter.GetUnitsX(0));
875 	if (~tabHistRight.opStaggered)
876 		tabHistLeft.scatter.PlotStyle<StaggeredSeriesPlot>().Dash("").NoMark().Fill(Blue()).Opacity(0.3).Stroke(2, LtBlue());
877 	tabHistLeft.scatter.ShowInfo().ShowContextMenu().ShowProcessDlg().ShowPropertiesDlg().SetMouseHandlingLinked(true, true);
878 	tabHistLeft.scatter.SetLabelX(isY ? tabFitLeft.scatter.GetLegend(0) : tabFitLeft.scatter.GetLabelX());
879 	tabHistLeft.scatter.SetLabelY(t_("Number"));
880 	tabHistLeft.scatter.ZoomToFit(true, true);
881 	double ymax = tabHistLeft.scatter.GetYMax();
882 	tabHistLeft.scatter.SetXYMin(Null, 0);
883 	tabHistLeft.scatter.SetRange(Null, ymax);
884 }
885 
886 }