Rework Data -> Statistics dialog, add WithReplacement and KeepOrder

* Add "With replacement" checkbutton
  * checked: WR (with replacement, put back after draw), larger
    sample size than population size possible
    * mutually exclusive with "Keep order"
  * unchecked: WOR (without replacement, do not put back after
    draw), sample size limited to population size
    * automatically unchecked for "Periodic" samples

* Add "Keep order" checkbutton
  * checked: samples are drawn in order of population data
    * WOR method mutually exclusive with "With replacement"
    * automatically checked for "Periodic" samples
  * unchecked: samples are drawn in random order

* Limit "Sample size" and "Period" input fields to size of
  population for all WOR methods
  * when limiting, increment "Sample size" and "Period" up to
    their last known value when enlargening the population size

Previously, for "Periodic" samples the "Period" field was never
limited, which for populations of smaller size makes no sense.

The new recent implementation for "Random" without "Keep order"
and without "With replacement" set (WOR) strictly needs "Sample
size" limited to population size.

Change-Id: I3c0bef402afb775bca7ae877e6424f4a4eaae359
Reviewed-on: https://gerrit.libreoffice.org/65574
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
This commit is contained in:
Eike Rathke 2018-12-22 22:06:02 +01:00
parent 28a1ae3285
commit 2c5c20b19c
3 changed files with 165 additions and 35 deletions

View file

@ -35,6 +35,8 @@ ScSamplingDialog::ScSamplingDialog(
mAddressDetails ( mDocument->GetAddressConvention(), 0, 0 ),
mOutputAddress ( ScAddress::INITIALIZE_INVALID ),
mCurrentAddress ( pViewData->GetCurX(), pViewData->GetCurY(), pViewData->GetTabNo() ),
mnLastSampleSizeValue(1),
mnLastPeriodValue(1),
mDialogLostFocus( false )
{
get(mpInputRangeLabel, "input-range-label");
@ -53,6 +55,8 @@ ScSamplingDialog::ScSamplingDialog(
get(mpPeriod, "period-spin");
get(mpRandomMethodRadio, "random-method-radio");
get(mpWithReplacement, "with-replacement");
get(mpKeepOrder, "keep-order");
get(mpPeriodicMethodRadio, "periodic-method-radio");
get(mpButtonOk, "ok");
@ -77,6 +81,8 @@ void ScSamplingDialog::dispose()
mpSampleSize.clear();
mpPeriod.clear();
mpRandomMethodRadio.clear();
mpWithReplacement.clear();
mpKeepOrder.clear();
mpPeriodicMethodRadio.clear();
mpButtonOk.clear();
mpActiveEdit.clear();
@ -105,12 +111,13 @@ void ScSamplingDialog::Init()
mpOutputRangeEdit->SetModifyHdl( aLink2);
mpSampleSize->SetModifyHdl( LINK( this, ScSamplingDialog, SamplingSizeValueModified ));
mpPeriod->SetModifyHdl( LINK( this, ScSamplingDialog, PeriodValueModified ));
mpPeriodicMethodRadio->SetToggleHdl( LINK( this, ScSamplingDialog, ToggleSamplingMethod ) );
mpRandomMethodRadio->SetToggleHdl( LINK( this, ScSamplingDialog, ToggleSamplingMethod ) );
mpSampleSize->SetMin( 0 );
mpSampleSize->SetMax( SAL_MAX_INT64 );
mpWithReplacement->SetClickHdl( LINK( this, ScSamplingDialog, CheckHdl));
mpKeepOrder->SetClickHdl( LINK( this, ScSamplingDialog, CheckHdl));
mpOutputRangeEdit->GrabFocus();
mpPeriodicMethodRadio->Check();
@ -159,6 +166,8 @@ void ScSamplingDialog::SetReference( const ScRange& rReferenceRange, ScDocument*
mInputRange = rReferenceRange;
aReferenceString = mInputRange.Format(ScRefFlags::RANGE_ABS_3D, pDocument, mAddressDetails);
mpInputRangeEdit->SetRefString( aReferenceString );
LimitSampleSizeAndPeriod();
}
else if ( mpActiveEdit == mpOutputRangeEdit )
{
@ -179,8 +188,11 @@ void ScSamplingDialog::SetReference( const ScRange& rReferenceRange, ScDocument*
}
// Enable OK if both, input range and output address are set.
// Disable if at least one is invalid.
if (mInputRange.IsValid() && mOutputAddress.IsValid())
mpButtonOk->Enable();
else
mpButtonOk->Enable(false);
}
ScRange ScSamplingDialog::PerformPeriodicSampling(ScDocShell* pDocShell)
@ -233,9 +245,7 @@ ScRange ScSamplingDialog::PerformRandomSampling(ScDocShell* pDocShell)
// by rows or area.
const sal_Int64 nPopulationSize = aEnd.Row() - aStart.Row() + 1;
/* TODO: the previously existing implementation was WOR, we may want to
* additionally offer WR as option. */
bool bWithReplacement = false;
const bool bWithReplacement = mpWithReplacement->IsEnabled() && mpWithReplacement->IsChecked();
// WOR (WithOutReplacement) can't draw more than population. Catch that in
// the caller.
@ -357,7 +367,10 @@ void ScSamplingDialog::PerformSampling()
if (mpRandomMethodRadio->IsChecked())
{
aModifiedRange = PerformRandomSampling(pDocShell);
if (mpKeepOrder->IsEnabled() && mpKeepOrder->IsChecked())
aModifiedRange = PerformRandomSamplingKeepOrder(pDocShell);
else
aModifiedRange = PerformRandomSampling(pDocShell);
}
else if (mpPeriodicMethodRadio->IsChecked())
{
@ -368,37 +381,68 @@ void ScSamplingDialog::PerformSampling()
pDocShell->PostPaint(aModifiedRange, PaintPartFlags::Grid);
}
sal_Int64 ScSamplingDialog::GetPopulationSize() const
{
return mInputRange.IsValid() ? mInputRange.aEnd.Row() - mInputRange.aStart.Row() + 1 : 0;
}
void ScSamplingDialog::LimitSampleSizeAndPeriod()
{
// Limit sample size (for WOR methods) and period if population is smaller
// than last known value. When enlargening the input population range the
// values will be adjusted up to the last known value again.
const sal_Int64 nPopulationSize = GetPopulationSize();
if (nPopulationSize <= mnLastSampleSizeValue && !mpWithReplacement->IsChecked())
mpSampleSize->SetValue( nPopulationSize);
if (nPopulationSize <= mnLastPeriodValue)
mpPeriod->SetValue( nPopulationSize);
}
IMPL_LINK_NOARG(ScSamplingDialog, SamplingSizeValueModified, Edit&, void)
{
if (!mpWithReplacement->IsChecked())
{
// For all WOR methods limit sample size to population size.
const sal_Int64 nPopulationSize = GetPopulationSize();
if (mpSampleSize->GetValue() > nPopulationSize)
mpSampleSize->SetValue(nPopulationSize);
}
mnLastSampleSizeValue = mpSampleSize->GetValue();
}
IMPL_LINK_NOARG(ScSamplingDialog, PeriodValueModified, Edit&, void)
{
// Limit period to population size.
const sal_Int64 nPopulationSize = GetPopulationSize();
if (mpPeriod->GetValue() > nPopulationSize)
mpPeriod->SetValue(nPopulationSize);
mnLastPeriodValue = mpPeriod->GetValue();
}
IMPL_LINK( ScSamplingDialog, GetFocusHandler, Control&, rCtrl, void )
{
if ( (&rCtrl == static_cast<Control*>(mpInputRangeEdit)) || (&rCtrl == static_cast<Control*>(mpInputRangeButton)))
mpActiveEdit = mpInputRangeEdit;
else if ((&rCtrl == static_cast<Control*>(mpOutputRangeEdit)) || (&rCtrl == static_cast<Control*>(mpOutputRangeButton)))
mpActiveEdit = mpOutputRangeEdit;
else
mpActiveEdit = nullptr;
if (mpActiveEdit)
mpActiveEdit->SetSelection( Selection( 0, SELECTION_MAX ) );
}
IMPL_LINK_NOARG( ScSamplingDialog, OkClicked, Button*, void )
{
PerformSampling();
Close();
}
IMPL_LINK( ScSamplingDialog, GetFocusHandler, Control&, rCtrl, void )
{
mpActiveEdit = nullptr;
if( (&rCtrl == static_cast<Control*>(mpInputRangeEdit)) || (&rCtrl == static_cast<Control*>(mpInputRangeButton)) )
mpActiveEdit = mpInputRangeEdit;
else if( (&rCtrl == static_cast<Control*>(mpOutputRangeEdit)) || (&rCtrl == static_cast<Control*>(mpOutputRangeButton)) )
mpActiveEdit = mpOutputRangeEdit;
if( mpActiveEdit )
mpActiveEdit->SetSelection( Selection( 0, SELECTION_MAX ) );
}
IMPL_LINK_NOARG(ScSamplingDialog, LoseFocusHandler, Control&, void)
{
mDialogLostFocus = !IsActive();
}
IMPL_LINK_NOARG(ScSamplingDialog, SamplingSizeValueModified, Edit&, void)
{
sal_Int64 aPopulationSize = mInputRange.aEnd.Row() - mInputRange.aStart.Row() + 1;
if (mpSampleSize->GetValue() > aPopulationSize)
mpSampleSize->SetValue(aPopulationSize);
}
IMPL_LINK_NOARG(ScSamplingDialog, ToggleSamplingMethod, RadioButton&, void)
{
ToggleSamplingMethod();
@ -410,11 +454,49 @@ void ScSamplingDialog::ToggleSamplingMethod()
{
mpPeriod->Enable(false);
mpSampleSize->Enable();
mpWithReplacement->Enable();
mpKeepOrder->Enable();
}
else if (mpPeriodicMethodRadio->IsChecked())
{
// WOR keeping order.
mpPeriod->Enable();
mpSampleSize->Enable(false);
mpWithReplacement->Check(false);
mpWithReplacement->Enable(false);
mpKeepOrder->Check();
mpKeepOrder->Enable(false);
}
}
IMPL_LINK( ScSamplingDialog, CheckHdl, Button*, pBtn, void )
{
// Keep both checkboxes enabled so user can easily switch between the three
// possible combinations (one or the other or none), just uncheck the other
// one if one is checked. Otherwise the other checkbox would had to be
// disabled until user unchecks the enabled one again, which would force
// user to two clicks to switch.
if (pBtn == mpWithReplacement)
{
if (static_cast<const CheckBox*>(pBtn)->IsChecked())
{
// For WR can't keep order.
mpKeepOrder->Check(false);
}
else
{
// For WOR limit sample size to population size.
SamplingSizeValueModified(*mpSampleSize);
}
}
else if (pBtn == mpKeepOrder)
{
if (static_cast<const CheckBox*>(pBtn)->IsChecked())
{
// Keep order is always WOR.
mpWithReplacement->Check(false);
SamplingSizeValueModified(*mpSampleSize);
}
}
}
@ -432,6 +514,8 @@ IMPL_LINK_NOARG(ScSamplingDialog, RefInputModifyHandler, Edit&, void)
mInputRange = *pRange;
// Highlight the resulting range.
mpInputRangeEdit->StartUpdateData();
LimitSampleSizeAndPeriod();
}
else
{

View file

@ -45,6 +45,8 @@ private:
VclPtr<NumericField> mpPeriod;
VclPtr<RadioButton> mpRandomMethodRadio;
VclPtr<CheckBox> mpWithReplacement;
VclPtr<CheckBox> mpKeepOrder;
VclPtr<RadioButton> mpPeriodicMethodRadio;
VclPtr<OKButton> mpButtonOk;
@ -61,11 +63,16 @@ private:
ScAddress const mCurrentAddress;
sal_Int64 mnLastSampleSizeValue;
sal_Int64 mnLastPeriodValue;
bool mDialogLostFocus;
void Init();
void GetRangeFromSelection();
void PerformSampling();
sal_Int64 GetPopulationSize() const;
void LimitSampleSizeAndPeriod();
ScRange PerformRandomSampling(ScDocShell* pDocShell);
ScRange PerformRandomSamplingKeepOrder(ScDocShell* pDocShell);
@ -75,8 +82,10 @@ private:
DECL_LINK( GetFocusHandler, Control&, void );
DECL_LINK( LoseFocusHandler, Control&, void );
DECL_LINK( SamplingSizeValueModified, Edit&, void );
DECL_LINK( PeriodValueModified, Edit&, void );
DECL_LINK( ToggleSamplingMethod, RadioButton&, void );
DECL_LINK( RefInputModifyHandler, Edit&, void );
DECL_LINK( CheckHdl, Button*, void );
void ToggleSamplingMethod();
};

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.22.1 -->
<interface domain="sc">
<requires lib="gtk+" version="3.18"/>
<requires lib="LibreOffice" version="1.0"/>
@ -21,6 +21,9 @@
<property name="title" translatable="yes" context="samplingdialog|SamplingDialog">Sampling</property>
<property name="resizable">False</property>
<property name="type_hint">dialog</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox1">
<property name="can_focus">False</property>
@ -109,10 +112,10 @@
<object class="GtkLabel" id="input-range-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes" context="samplingdialog|input-range-label">Input range:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">input-range-edit</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
@ -147,10 +150,10 @@
<object class="GtkLabel" id="output-range-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes" context="samplingdialog|output-range-label">Results to:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">output-range-edit</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
@ -227,9 +230,10 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="text" translatable="no">0</property>
<property name="text">1</property>
<property name="adjustment">sample-size-adjustment</property>
<property name="update_policy">if-valid</property>
<property name="value">1</property>
</object>
<packing>
<property name="left_attach">1</property>
@ -242,12 +246,12 @@
<property name="can_focus">False</property>
<property name="margin_left">12</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes" context="samplingdialog|label1">Sample size:</property>
<property name="use_underline">True</property>
<property name="justify">center</property>
<property name="mnemonic_widget">sample-size-spin</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
@ -284,7 +288,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="top_attach">4</property>
<property name="width">2</property>
</packing>
</child>
@ -293,13 +297,14 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="text" translatable="no">1</property>
<property name="text">1</property>
<property name="adjustment">period-adjustment</property>
<property name="update_policy">if-valid</property>
<property name="value">1</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
@ -308,15 +313,47 @@
<property name="can_focus">False</property>
<property name="margin_left">12</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes" context="samplingdialog|label3">Period:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">period-spin</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="with-replacement">
<property name="label" translatable="yes" context="samplingdialog|with-replacement">With replacement</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="keep-order">
<property name="label" translatable="yes" context="samplingdialog|keep-order">Keep order</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="width">2</property>
</packing>
</child>
</object>