mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-09 23:16:00 -06:00
#4185 Implement data export for Grid Cross Plot
This commit is contained in:
parent
6008b8a45d
commit
e72f57072c
@ -20,6 +20,9 @@
|
||||
|
||||
#include "RiaApplication.h"
|
||||
|
||||
#include "RimGridCrossPlot.h"
|
||||
#include "RimGridCrossPlotCurve.h"
|
||||
#include "RimGridCrossPlotCurveSet.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCrossPlot.h"
|
||||
#include "RimSummaryPlot.h"
|
||||
@ -28,6 +31,8 @@
|
||||
#include "RiuPlotMainWindow.h"
|
||||
#include "RiuTextDialog.h"
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
#include "cafProgressInfo.h"
|
||||
#include "cafSelectionManagerTools.h"
|
||||
|
||||
#include <QAction>
|
||||
@ -35,6 +40,128 @@
|
||||
|
||||
CAF_CMD_SOURCE_INIT(RicShowPlotDataFeature, "RicShowPlotDataFeature");
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Private text provider class for summary plots
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class RiuTabbedSummaryPlotTextProvider : public RiuTabbedTextProvider
|
||||
{
|
||||
public:
|
||||
RiuTabbedSummaryPlotTextProvider(RimSummaryPlot* summaryPlot)
|
||||
: m_summaryPlot(summaryPlot)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool isValid() const override
|
||||
{
|
||||
return m_summaryPlot.notNull();
|
||||
}
|
||||
|
||||
QString description() const override
|
||||
{
|
||||
CVF_ASSERT(m_summaryPlot.notNull() && "Need to check that provider is valid");
|
||||
return m_summaryPlot->description();
|
||||
}
|
||||
|
||||
QString tabTitle(int tabIndex) const override
|
||||
{
|
||||
auto allTabs = tabs();
|
||||
CVF_ASSERT(tabIndex < (int)allTabs.size());
|
||||
DateTimePeriod timePeriod = allTabs[tabIndex];
|
||||
if (timePeriod == DateTimePeriod::NONE)
|
||||
{
|
||||
return "No Resampling";
|
||||
}
|
||||
else
|
||||
{
|
||||
return QString("Plot Data, %1").arg(RiaQDateTimeTools::dateTimePeriodName(timePeriod));
|
||||
}
|
||||
}
|
||||
|
||||
QString tabText(int tabIndex) const override
|
||||
{
|
||||
CVF_ASSERT(m_summaryPlot.notNull() && "Need to check that provider is valid");
|
||||
|
||||
DateTimePeriod timePeriod = indexToPeriod(tabIndex);
|
||||
|
||||
if (m_summaryPlot->containsResamplableCurves())
|
||||
{
|
||||
return m_summaryPlot->asciiDataForPlotExport(timePeriod);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_summaryPlot->asciiDataForPlotExport();
|
||||
}
|
||||
}
|
||||
|
||||
int tabCount() const override
|
||||
{
|
||||
return (int)tabs().size();
|
||||
}
|
||||
|
||||
private:
|
||||
static DateTimePeriod indexToPeriod(int tabIndex)
|
||||
{
|
||||
auto allTabs = tabs();
|
||||
CVF_ASSERT(tabIndex < (int)allTabs.size());
|
||||
DateTimePeriod timePeriod = allTabs[tabIndex];
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
static std::vector<DateTimePeriod> tabs()
|
||||
{
|
||||
std::vector<DateTimePeriod> dateTimePeriods = RiaQDateTimeTools::dateTimePeriods();
|
||||
dateTimePeriods.erase(std::remove(dateTimePeriods.begin(), dateTimePeriods.end(), DateTimePeriod::DECADE),
|
||||
dateTimePeriods.end());
|
||||
return dateTimePeriods;
|
||||
}
|
||||
|
||||
private:
|
||||
caf::PdmPointer<RimSummaryPlot> m_summaryPlot;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Private text provider class for grid cross plots
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class RiuTabbedGridCrossPlotTextProvider : public RiuTabbedTextProvider
|
||||
{
|
||||
public:
|
||||
RiuTabbedGridCrossPlotTextProvider(RimGridCrossPlot* crossPlot)
|
||||
: m_crossPlot(crossPlot)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool isValid() const override
|
||||
{
|
||||
return m_crossPlot.notNull();
|
||||
}
|
||||
|
||||
virtual QString description() const override
|
||||
{
|
||||
CVF_ASSERT(m_crossPlot.notNull() && "Need to check that provider is valid");
|
||||
return m_crossPlot->createAutoName();
|
||||
}
|
||||
|
||||
virtual QString tabTitle(int tabIndex) const override
|
||||
{
|
||||
CVF_ASSERT(m_crossPlot.notNull() && "Need to check that provider is valid");
|
||||
return m_crossPlot->asciiTitleForPlotExport(tabIndex);
|
||||
}
|
||||
|
||||
virtual QString tabText(int tabIndex) const override
|
||||
{
|
||||
CVF_ASSERT(m_crossPlot.notNull() && "Need to check that provider is valid");
|
||||
return m_crossPlot->asciiDataForPlotExport(tabIndex);
|
||||
}
|
||||
|
||||
virtual int tabCount() const override
|
||||
{
|
||||
CVF_ASSERT(m_crossPlot.notNull() && "Need to check that provider is valid");
|
||||
return (int)m_crossPlot->curveSets().size();
|
||||
}
|
||||
|
||||
private:
|
||||
caf::PdmPointer<RimGridCrossPlot> m_crossPlot;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
@ -62,6 +189,9 @@ bool RicShowPlotDataFeature::isCommandEnabled()
|
||||
auto wellLogPlots = caf::selectedObjectsByType<RimWellLogPlot*>();
|
||||
if (wellLogPlots.size() > 0) return true;
|
||||
|
||||
auto gridCrossPlots = caf::selectedObjectsByType<RimGridCrossPlot*>();
|
||||
if (gridCrossPlots.size() > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -72,10 +202,10 @@ void RicShowPlotDataFeature::onActionTriggered(bool isChecked)
|
||||
{
|
||||
this->disableModelChangeContribution();
|
||||
|
||||
std::vector<RimSummaryPlot*> selectedSummaryPlots = caf::selectedObjectsByType<RimSummaryPlot*>();
|
||||
std::vector<RimWellLogPlot*> wellLogPlots = caf::selectedObjectsByType<RimWellLogPlot*>();
|
||||
|
||||
if (selectedSummaryPlots.size() == 0 && wellLogPlots.size() == 0)
|
||||
std::vector<RimSummaryPlot*> selectedSummaryPlots = caf::selectedObjectsByType<RimSummaryPlot*>();
|
||||
std::vector<RimWellLogPlot*> wellLogPlots = caf::selectedObjectsByType<RimWellLogPlot*>();
|
||||
std::vector<RimGridCrossPlot*> crossPlots = caf::selectedObjectsByType<RimGridCrossPlot*>();
|
||||
if (selectedSummaryPlots.size() == 0 && wellLogPlots.size() == 0 && crossPlots.size() == 0)
|
||||
{
|
||||
CVF_ASSERT(false);
|
||||
|
||||
@ -87,26 +217,22 @@ void RicShowPlotDataFeature::onActionTriggered(bool isChecked)
|
||||
|
||||
for (RimSummaryPlot* summaryPlot : selectedSummaryPlots)
|
||||
{
|
||||
QString title = summaryPlot->description();
|
||||
|
||||
if (summaryPlot->containsResamplableCurves())
|
||||
{
|
||||
RicShowPlotDataFeature::showTabbedTextWindow(title, [summaryPlot](DateTimePeriod period) { return summaryPlot->asciiDataForPlotExport(period); });
|
||||
}
|
||||
else
|
||||
{
|
||||
QString text = summaryPlot->asciiDataForPlotExport();
|
||||
RicShowPlotDataFeature::showTextWindow(title, text);
|
||||
}
|
||||
auto textProvider = new RiuTabbedSummaryPlotTextProvider(summaryPlot);
|
||||
RicShowPlotDataFeature::showTabbedTextWindow(textProvider);
|
||||
}
|
||||
|
||||
for (RimWellLogPlot* wellLogPlot : wellLogPlots)
|
||||
{
|
||||
QString title = wellLogPlot->description();
|
||||
QString text = wellLogPlot->asciiDataForPlotExport();
|
||||
|
||||
RicShowPlotDataFeature::showTextWindow(title, text);
|
||||
}
|
||||
|
||||
for (RimGridCrossPlot* crossPlot : crossPlots)
|
||||
{
|
||||
auto textProvider = new RiuTabbedGridCrossPlotTextProvider(crossPlot);
|
||||
RicShowPlotDataFeature::showTabbedTextWindow(textProvider);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -118,24 +244,19 @@ void RicShowPlotDataFeature::setupActionLook(QAction* actionToSetup)
|
||||
actionToSetup->setIcon(QIcon(":/PlotWindow24x24.png"));
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicShowPlotDataFeature::showTabbedTextWindow(const QString& title, std::function<QString(DateTimePeriod)> textProvider)
|
||||
void RicShowPlotDataFeature::showTabbedTextWindow(RiuTabbedTextProvider* textProvider)
|
||||
{
|
||||
RiuPlotMainWindow* plotwindow = RiaApplication::instance()->mainPlotWindow();
|
||||
CVF_ASSERT(plotwindow);
|
||||
|
||||
RiuShowTabbedPlotDataDialog* textWidget = new RiuShowTabbedPlotDataDialog();
|
||||
RiuTabbedTextDialog* textWidget = new RiuTabbedTextDialog(textProvider);
|
||||
textWidget->setMinimumSize(800, 600);
|
||||
|
||||
textWidget->setWindowTitle(title);
|
||||
textWidget->setDescription(title);
|
||||
textWidget->setTextProvider(textProvider);
|
||||
textWidget->show();
|
||||
|
||||
plotwindow->addToTemporaryWidgets(textWidget);
|
||||
textWidget->show();
|
||||
textWidget->redrawText();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
class RiuTabbedTextProvider;
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
@ -38,7 +40,7 @@ protected:
|
||||
void setupActionLook( QAction* actionToSetup ) override;
|
||||
|
||||
public:
|
||||
static void showTabbedTextWindow(const QString& title, std::function<QString(DateTimePeriod)> textProvider);
|
||||
static void showTabbedTextWindow(RiuTabbedTextProvider* textProvider);
|
||||
static void showTextWindow(const QString& title, const QString& text);
|
||||
};
|
||||
|
||||
|
@ -106,7 +106,7 @@ void RicExportSelectedWellPathsFeature::writeWellPathGeometryToStream(QTextStrea
|
||||
double endMd = wellPathGeom->measureDepths().back();
|
||||
|
||||
RifEclipseDataTableFormatter formatter(stream);
|
||||
formatter.setCommentPrefix("#");
|
||||
formatter.setCommentPrefix("# ");
|
||||
formatter.setTableRowPrependText(" ");
|
||||
|
||||
if (writeProjectInfo)
|
||||
|
@ -32,7 +32,7 @@ RifEclipseDataTableFormatter::RifEclipseDataTableFormatter(QTextStream& out)
|
||||
, m_colSpacing(5)
|
||||
, m_tableRowPrependText(" ")
|
||||
, m_tableRowAppendText(" /")
|
||||
, m_commentPrefix("--")
|
||||
, m_commentPrefix("-- ")
|
||||
, m_maxDataRowWidth(MAX_ECLIPSE_DATA_ROW_WIDTH)
|
||||
{
|
||||
}
|
||||
@ -154,7 +154,7 @@ void RifEclipseDataTableFormatter::outputBuffer()
|
||||
{
|
||||
if (!m_columns.empty() && !isAllHeadersEmpty(m_columns))
|
||||
{
|
||||
m_out << m_commentPrefix << " ";
|
||||
m_out << m_commentPrefix;
|
||||
for (size_t i = 0u; i < m_columns.size(); ++i)
|
||||
{
|
||||
m_out << formatColumn(m_columns[i].title, i);
|
||||
@ -206,7 +206,7 @@ void RifEclipseDataTableFormatter::outputBuffer()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RifEclipseDataTableFormatter::outputComment(RifEclipseOutputTableLine& comment)
|
||||
{
|
||||
m_out << m_commentPrefix << " " << comment.data[0] << "\n";
|
||||
m_out << m_commentPrefix << comment.data[0] << "\n";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@ -17,6 +17,7 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
#include "RimGridCrossPlot.h"
|
||||
|
||||
#include "RifEclipseDataTableFormatter.h"
|
||||
#include "RiuGridCrossQwtPlot.h"
|
||||
#include "RiuPlotMainWindowTools.h"
|
||||
#include "RiuQwtPlotTools.h"
|
||||
@ -28,6 +29,7 @@
|
||||
#include "cafPdmUiCheckBoxEditor.h"
|
||||
#include "cafPdmUiTreeOrdering.h"
|
||||
|
||||
#include "cafProgressInfo.h"
|
||||
#include "cvfAssert.h"
|
||||
|
||||
#include "qwt_legend.h"
|
||||
@ -520,6 +522,45 @@ void RimGridCrossPlot::swapAllAxisProperties()
|
||||
loadDataAndUpdate();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimGridCrossPlot::asciiTitleForPlotExport(int curveSetIndex) const
|
||||
{
|
||||
if ((size_t)curveSetIndex < m_crossPlotCurveSets.size())
|
||||
{
|
||||
return m_crossPlotCurveSets[curveSetIndex]->createAutoName();
|
||||
}
|
||||
return "Data invalid";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimGridCrossPlot::asciiDataForPlotExport(int curveSetIndex) const
|
||||
{
|
||||
if ((size_t)curveSetIndex < m_crossPlotCurveSets.size())
|
||||
{
|
||||
QString asciiData;
|
||||
QTextStream stringStream(&asciiData);
|
||||
|
||||
RifEclipseDataTableFormatter formatter(stringStream);
|
||||
formatter.setCommentPrefix("");
|
||||
formatter.setTableRowPrependText("");
|
||||
formatter.setTableRowLineAppendText("");
|
||||
formatter.setColumnSpacing(3);
|
||||
|
||||
|
||||
m_crossPlotCurveSets[curveSetIndex]->exportFormattedData(formatter);
|
||||
formatter.tableCompleted();
|
||||
return asciiData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Data invalid and may have been deleted.";
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@ -70,8 +70,10 @@ public:
|
||||
void performAutoNameUpdate() override;
|
||||
void updateCurveNamesAndPlotTitle();
|
||||
void swapAllAxisProperties();
|
||||
|
||||
QString asciiTitleForPlotExport(int curveSetIndex) const;
|
||||
QString asciiDataForPlotExport(int curveSetIndex) const;
|
||||
RiuGridCrossQwtPlot* qwtPlot() const;
|
||||
|
||||
public:
|
||||
// Rim2dPlotInterface overrides
|
||||
void updateAxisScaling() override;
|
||||
@ -112,6 +114,7 @@ private:
|
||||
caf::PdmChildArrayField<RimGridCrossPlotCurveSet*> m_crossPlotCurveSets;
|
||||
|
||||
QPointer<RiuGridCrossQwtPlot> m_qwtPlot;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
~RimGridCrossPlotCurve() override = default;
|
||||
void setGroupingInformation(int curveSetIndex, int groupIndex);
|
||||
void setSamples(const std::vector<double>& xValues, const std::vector<double>& yValues);
|
||||
|
||||
void updateCurveAppearance() override;
|
||||
int groupIndex() const;
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include "RiaColorTables.h"
|
||||
#include "RiaLogging.h"
|
||||
|
||||
#include "RifEclipseDataTableFormatter.h"
|
||||
|
||||
#include "RigActiveCellInfo.h"
|
||||
#include "RigActiveCellsResultAccessor.h"
|
||||
#include "RigCaseCellResultCalculator.h"
|
||||
@ -44,11 +46,14 @@
|
||||
#include "RimRegularLegendConfig.h"
|
||||
#include "RimTools.h"
|
||||
|
||||
#include "cafCategoryMapper.h"
|
||||
#include "cafColorTable.h"
|
||||
#include "cafPdmUiComboBoxEditor.h"
|
||||
#include "cafPdmUiSliderEditor.h"
|
||||
#include "cafPdmUiTreeOrdering.h"
|
||||
#include "cafProgressInfo.h"
|
||||
#include "cvfScalarMapper.h"
|
||||
#include "cvfqtUtils.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
@ -210,7 +215,11 @@ QString RimGridCrossPlotCurveSet::createAutoName() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimGridCrossPlotCurveSet::groupTitle() const
|
||||
{
|
||||
return QString("Grouping by %1").arg(groupParameter());
|
||||
if (m_grouping != NO_GROUPING)
|
||||
{
|
||||
return QString("Grouped by %1").arg(groupParameter());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -410,6 +419,7 @@ void RimGridCrossPlotCurveSet::onLoadDataAndUpdate(bool updateParentPlot)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& result)
|
||||
{
|
||||
m_groupedResults.clear();
|
||||
if (!groupingEnabled())
|
||||
{
|
||||
const caf::ColorTable& colors = RiaColorTables::contrastCategoryPaletteColors();
|
||||
@ -422,11 +432,10 @@ void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& res
|
||||
curve->updateCurveAppearance();
|
||||
curve->updateUiIconFromPlotSymbol();
|
||||
m_crossPlotCurves.push_back(curve);
|
||||
m_groupedResults[0] = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<int, std::pair<std::vector<double>, std::vector<double>>> groupedResults;
|
||||
|
||||
std::vector<double> tickValues;
|
||||
|
||||
if (groupingByCategoryResult())
|
||||
@ -438,8 +447,13 @@ void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& res
|
||||
? static_cast<int>(result.groupValuesContinuous[i])
|
||||
: result.groupValuesDiscrete[i];
|
||||
|
||||
groupedResults[categoryNum].first.push_back(result.xValues[i]);
|
||||
groupedResults[categoryNum].second.push_back(result.yValues[i]);
|
||||
m_groupedResults[categoryNum].xValues.push_back(result.xValues[i]);
|
||||
m_groupedResults[categoryNum].yValues.push_back(result.yValues[i]);
|
||||
if (!result.groupValuesContinuous.empty())
|
||||
m_groupedResults[categoryNum].groupValuesContinuous.push_back(result.groupValuesContinuous[i]);
|
||||
if (!result.groupValuesDiscrete.empty())
|
||||
m_groupedResults[categoryNum].groupValuesDiscrete.push_back(result.groupValuesDiscrete[i]);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -450,13 +464,17 @@ void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& res
|
||||
{
|
||||
auto upperBoundIt = std::lower_bound(tickValues.begin(), tickValues.end(), result.groupValuesContinuous[i]);
|
||||
int upperBoundIndex = static_cast<int>(upperBoundIt - tickValues.begin());
|
||||
int lowerBoundIndex = std::min((int) tickValues.size() - 2, std::max(0, upperBoundIndex - 1));
|
||||
groupedResults[lowerBoundIndex].first.push_back(result.xValues[i]);
|
||||
groupedResults[lowerBoundIndex].second.push_back(result.yValues[i]);
|
||||
int categoryNum = std::min((int) tickValues.size() - 2, std::max(0, upperBoundIndex - 1));
|
||||
m_groupedResults[categoryNum].xValues.push_back(result.xValues[i]);
|
||||
m_groupedResults[categoryNum].yValues.push_back(result.yValues[i]);
|
||||
if (!result.groupValuesContinuous.empty())
|
||||
m_groupedResults[categoryNum].groupValuesContinuous.push_back(result.groupValuesContinuous[i]);
|
||||
if (!result.groupValuesDiscrete.empty())
|
||||
m_groupedResults[categoryNum].groupValuesDiscrete.push_back(result.groupValuesDiscrete[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = groupedResults.rbegin(); it != groupedResults.rend(); ++it)
|
||||
for (auto it = m_groupedResults.rbegin(); it != m_groupedResults.rend(); ++it)
|
||||
{
|
||||
RimGridCrossPlotCurve* curve = new RimGridCrossPlotCurve();
|
||||
curve->setGroupingInformation(indexInPlot(), it->first);
|
||||
@ -468,9 +486,9 @@ void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& res
|
||||
{
|
||||
curve->setColor(cvf::Color3f(legendConfig()->scalarMapper()->mapToColor(tickValues[it->first])));
|
||||
}
|
||||
curve->setSamples(it->second.first, it->second.second);
|
||||
curve->setSamples(it->second.xValues, it->second.yValues);
|
||||
curve->showLegend(m_crossPlotCurves.empty());
|
||||
curve->setLegendEntryTitle(createAutoName());
|
||||
curve->setLegendEntryText(createAutoName());
|
||||
curve->updateCurveAppearance();
|
||||
curve->updateUiIconFromPlotSymbol();
|
||||
m_crossPlotCurves.push_back(curve);
|
||||
@ -478,6 +496,26 @@ void RimGridCrossPlotCurveSet::createCurves(const RigEclipseCrossPlotResult& res
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimGridCrossPlotCurveSet::createGroupName(size_t groupIndex) const
|
||||
{
|
||||
if (groupingByCategoryResult())
|
||||
{
|
||||
return legendConfig()->categoryNameFromCategoryValue(groupIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<double> tickValues;
|
||||
legendConfig()->scalarMapper()->majorTickValues(&tickValues);
|
||||
double lowerLimit = tickValues[groupIndex];
|
||||
double upperLimit =
|
||||
groupIndex + 1u < tickValues.size() ? tickValues[groupIndex + 1u] : std::numeric_limits<double>::infinity();
|
||||
return QString("%1 [%2, %3]").arg(groupParameter()).arg(lowerLimit).arg(upperLimit);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -786,6 +824,56 @@ void RimGridCrossPlotCurveSet::swapAxisProperties(bool updatePlot)
|
||||
loadDataAndUpdate(updatePlot);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimGridCrossPlotCurveSet::exportFormattedData(RifEclipseDataTableFormatter& formatter) const
|
||||
{
|
||||
if (m_groupedResults.empty())
|
||||
return;
|
||||
|
||||
if (m_grouping != NO_GROUPING)
|
||||
{
|
||||
std::vector<RifEclipseOutputTableColumn> header = {RifEclipseOutputTableColumn("X"),
|
||||
RifEclipseOutputTableColumn("Y"),
|
||||
RifEclipseOutputTableColumn("Group Index"),
|
||||
RifEclipseOutputTableColumn("Group Description")};
|
||||
|
||||
formatter.header(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<RifEclipseOutputTableColumn> header = {RifEclipseOutputTableColumn("X"), RifEclipseOutputTableColumn("Y")};
|
||||
formatter.header(header);
|
||||
}
|
||||
|
||||
caf::ProgressInfo progress(m_groupedResults.size(), "Gathering Data Points");
|
||||
for (auto it = m_groupedResults.begin(); it != m_groupedResults.end(); ++it)
|
||||
{
|
||||
int groupIndex = it->first;
|
||||
RigEclipseCrossPlotResult res = it->second;
|
||||
|
||||
for (size_t i = 0; i < it->second.xValues.size(); ++i)
|
||||
{
|
||||
if (m_grouping() == NO_GROUPING)
|
||||
{
|
||||
formatter.add(res.xValues[i]);
|
||||
formatter.add(res.yValues[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
QString groupName = createGroupName(groupIndex);
|
||||
formatter.add(res.xValues[i]);
|
||||
formatter.add(res.yValues[i]);
|
||||
formatter.add(groupIndex);
|
||||
formatter.add(groupName);
|
||||
}
|
||||
formatter.rowCompleted();
|
||||
}
|
||||
progress.incrementProgress();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -809,29 +897,17 @@ void RimGridCrossPlotCurveSet::updateCurveNames(size_t curveSetIndex, size_t cur
|
||||
if (curveSetName.isEmpty())
|
||||
{
|
||||
if (curveSetCount > 1u)
|
||||
curveSetName = QString("Curve #%1").arg(curveSetIndex + 1);
|
||||
curveSetName = QString("Curve Set #%1").arg(curveSetIndex + 1);
|
||||
else
|
||||
curveSetName = "Curve";
|
||||
curveSetName = "Curve Set";
|
||||
}
|
||||
|
||||
auto curve = m_crossPlotCurves[i];
|
||||
if (groupingEnabled())
|
||||
{
|
||||
if (groupingByCategoryResult())
|
||||
{
|
||||
curve->setCustomName(legendConfig()->categoryNameFromCategoryValue(curve->groupIndex()));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<double> tickValues;
|
||||
legendConfig()->scalarMapper()->majorTickValues(&tickValues);
|
||||
size_t catIndex = (size_t) curve->groupIndex();
|
||||
double lowerLimit = tickValues[catIndex];
|
||||
double upperLimit = catIndex + 1u < tickValues.size()
|
||||
? tickValues[catIndex + 1u] : std::numeric_limits<double>::infinity();
|
||||
curve->setCustomName(QString("%1 [%2, %3]").arg(groupParameter()).arg(lowerLimit).arg(upperLimit));
|
||||
}
|
||||
curve->setLegendEntryTitle(curveSetName);
|
||||
QString curveGroupName = createGroupName(curve->groupIndex());
|
||||
curve->setCustomName(curveGroupName);
|
||||
curve->setLegendEntryText(curveSetName);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "RigGridCrossPlotCurveGrouping.h"
|
||||
#include "RigEclipseCrossPlotDataExtractor.h"
|
||||
|
||||
#include "RimCheckableNamedObject.h"
|
||||
#include "RimNameConfig.h"
|
||||
@ -34,7 +35,7 @@
|
||||
#include <QList>
|
||||
#include <map>
|
||||
|
||||
struct RigEclipseCrossPlotResult;
|
||||
class RifEclipseDataTableFormatter;
|
||||
class RimCase;
|
||||
class RimGridCrossPlotCurve;
|
||||
class RimGridView;
|
||||
@ -114,12 +115,14 @@ public:
|
||||
bool groupingByCategoryResult() const;
|
||||
bool groupingEnabled() const;
|
||||
void swapAxisProperties(bool updatePlot);
|
||||
void exportFormattedData(RifEclipseDataTableFormatter& formatter) const;
|
||||
|
||||
protected:
|
||||
void initAfterRead() override;
|
||||
void onLoadDataAndUpdate(bool updateParentPlot);
|
||||
|
||||
void createCurves(const RigEclipseCrossPlotResult& result);
|
||||
QString createGroupName(size_t curveIndex) const;
|
||||
|
||||
std::map<int, cvf::UByteArray> calculateCellVisibility(RimEclipseCase* eclipseCase) const;
|
||||
|
||||
@ -137,6 +140,7 @@ protected:
|
||||
|
||||
bool hasMultipleTimeSteps() const;
|
||||
private:
|
||||
|
||||
caf::PdmPtrField<RimCase*> m_case;
|
||||
caf::PdmField<int> m_timeStep;
|
||||
caf::PdmPtrField<RimGridView*> m_cellFilterView;
|
||||
@ -148,4 +152,6 @@ private:
|
||||
caf::PdmChildField<RimGridCrossPlotCurveSetNameConfig*> m_nameConfig;
|
||||
|
||||
caf::PdmChildArrayField<RimGridCrossPlotCurve*> m_crossPlotCurves;
|
||||
|
||||
std::map<int, RigEclipseCrossPlotResult> m_groupedResults;
|
||||
};
|
||||
|
@ -96,8 +96,8 @@ RimPlotCurve::RimPlotCurve()
|
||||
CAF_PDM_InitFieldNoDefault(&m_customCurveName, "CurveDescription", "Custom Name", "", "", "");
|
||||
m_customCurveName.uiCapability()->setUiHidden(true);
|
||||
|
||||
CAF_PDM_InitFieldNoDefault(&m_legendEntryTitle, "LegendDescription", "Legend Name", "", "", "");
|
||||
m_legendEntryTitle.uiCapability()->setUiHidden(true);
|
||||
CAF_PDM_InitFieldNoDefault(&m_legendEntryText, "LegendDescription", "Legend Name", "", "", "");
|
||||
m_legendEntryText.uiCapability()->setUiHidden(true);
|
||||
|
||||
CAF_PDM_InitField(&m_isUsingAutoName, "AutoName", true, "Auto Name", "", "", "");
|
||||
|
||||
@ -219,9 +219,21 @@ void RimPlotCurve::setCustomName(const QString& customName)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimPlotCurve::setLegendEntryTitle(const QString& legendEntryTitle)
|
||||
QString RimPlotCurve::legendEntryText() const
|
||||
{
|
||||
m_legendEntryTitle = legendEntryTitle;
|
||||
if (!m_legendEntryText().isEmpty())
|
||||
{
|
||||
return m_legendEntryText;
|
||||
}
|
||||
return m_customCurveName;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimPlotCurve::setLegendEntryText(const QString& legendEntryText)
|
||||
{
|
||||
m_legendEntryText = legendEntryText;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -378,9 +390,9 @@ void RimPlotCurve::updateCurveName()
|
||||
m_curveName = m_customCurveName;
|
||||
}
|
||||
|
||||
if (!m_legendEntryTitle().isEmpty())
|
||||
if (!m_legendEntryText().isEmpty())
|
||||
{
|
||||
m_qwtPlotCurve->setTitle(m_legendEntryTitle);
|
||||
m_qwtPlotCurve->setTitle(m_legendEntryText);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -78,7 +78,8 @@ public:
|
||||
QString curveName() const { return m_curveName; }
|
||||
virtual QString curveExportDescription(const RifEclipseSummaryAddress& address = RifEclipseSummaryAddress()) const { return m_curveName; }
|
||||
void setCustomName(const QString& customName);
|
||||
void setLegendEntryTitle(const QString& legendEntryTitle);
|
||||
QString legendEntryText() const;
|
||||
void setLegendEntryText(const QString& legendEntryText);
|
||||
|
||||
void updateCurveVisibility(bool updateParentPlot);
|
||||
void updateLegendEntryVisibilityAndPlotLegend();
|
||||
@ -127,7 +128,7 @@ protected:
|
||||
caf::PdmField<bool> m_showLegend;
|
||||
QString m_symbolLabel;
|
||||
caf::PdmField<int> m_symbolSize;
|
||||
caf::PdmField<QString> m_legendEntryTitle;
|
||||
caf::PdmField<QString> m_legendEntryText;
|
||||
|
||||
caf::PdmField<bool> m_isUsingAutoName;
|
||||
caf::PdmField<cvf::Color3f> m_curveColor;
|
||||
|
@ -24,8 +24,11 @@
|
||||
#include "RimGridCrossPlotCurveSet.h"
|
||||
#include "RimRegularLegendConfig.h"
|
||||
|
||||
#include "cafCmdFeatureMenuBuilder.h"
|
||||
#include "cafSelectionManager.h"
|
||||
#include "cafTitledOverlayFrame.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QResizeEvent>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -207,3 +210,24 @@ bool RiuGridCrossQwtPlot::resizeOverlayItemToFitPlot(caf::TitledOverlayFrame* ov
|
||||
overlayItem->setRenderSize(legendSize);
|
||||
return sizeAltered;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuGridCrossQwtPlot::contextMenuEvent(QContextMenuEvent* event)
|
||||
{
|
||||
QMenu menu;
|
||||
caf::CmdFeatureMenuBuilder menuBuilder;
|
||||
|
||||
caf::SelectionManager::instance()->setSelectedItem(ownerViewWindow());
|
||||
|
||||
menuBuilder << "RicShowPlotDataFeature";
|
||||
|
||||
menuBuilder.appendToMenu(&menu);
|
||||
|
||||
if (menu.actions().size() > 0)
|
||||
{
|
||||
menu.exec(event->globalPos());
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ protected:
|
||||
void updateLegendLayout();
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
bool resizeOverlayItemToFitPlot(caf::TitledOverlayFrame* overlayItem);
|
||||
void contextMenuEvent(QContextMenuEvent*) override;
|
||||
private:
|
||||
std::map<caf::PdmPointer<RimGridCrossPlotCurveSet>, QPointer<RiuCvfOverlayItemWidget>> m_legendWidgets;
|
||||
};
|
||||
|
@ -101,12 +101,12 @@ void RiuQPlainTextEdit::slotSelectAll()
|
||||
void RiuQPlainTextEdit::slotExportToFile()
|
||||
{
|
||||
// Get dialog
|
||||
RiuShowTabbedPlotDataDialog* dialog = nullptr;
|
||||
RiuTabbedTextDialog* dialog = nullptr;
|
||||
auto curr = parent();
|
||||
while (dialog == nullptr)
|
||||
{
|
||||
if (!curr) break;
|
||||
dialog = dynamic_cast<RiuShowTabbedPlotDataDialog*>(curr);
|
||||
dialog = dynamic_cast<RiuTabbedTextDialog*>(curr);
|
||||
if (dialog) break;
|
||||
curr = curr->parent();
|
||||
}
|
||||
@ -190,21 +190,19 @@ void RiuTextDialog::contextMenuEvent(QContextMenuEvent* event)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiuShowTabbedPlotDataDialog::RiuShowTabbedPlotDataDialog(QWidget* parent /*= nullptr*/)
|
||||
: QDialog(parent, RiuTools::defaultDialogFlags())
|
||||
RiuTabbedTextDialog::RiuTabbedTextDialog(RiuTabbedTextProvider* textProvider, QWidget* parent /*= nullptr*/)
|
||||
: m_textProvider(textProvider), QDialog(parent, RiuTools::defaultDialogFlags())
|
||||
{
|
||||
m_tabWidget = new QTabWidget(this);
|
||||
|
||||
connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int)));
|
||||
|
||||
for(auto timePeriod : RiaQDateTimeTools::dateTimePeriods())
|
||||
CVF_ASSERT(m_textProvider->isValid());
|
||||
this->setWindowTitle(m_textProvider->description());
|
||||
|
||||
for (int tabIndex = 0; tabIndex < m_textProvider->tabCount(); ++tabIndex)
|
||||
{
|
||||
if(timePeriod == DateTimePeriod::DECADE) continue;
|
||||
|
||||
QString tabTitle =
|
||||
timePeriod == DateTimePeriod::NONE ? "No Resampling" :
|
||||
QString("Plot Data, %1").arg(RiaQDateTimeTools::dateTimePeriodName(timePeriod));
|
||||
|
||||
QString tabTitle = m_textProvider->tabTitle(tabIndex);
|
||||
RiuQPlainTextEdit* textEdit = new RiuQPlainTextEdit();
|
||||
textEdit->setReadOnly(true);
|
||||
textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
@ -218,43 +216,56 @@ RiuShowTabbedPlotDataDialog::RiuShowTabbedPlotDataDialog(QWidget* parent /*= nul
|
||||
|
||||
m_tabWidget->addTab(textEdit, tabTitle);
|
||||
}
|
||||
m_tabTexts.resize(m_textProvider->tabCount());
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->addWidget(m_tabWidget);
|
||||
setLayout(layout);
|
||||
|
||||
updateTabText();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuShowTabbedPlotDataDialog::setDescription(const QString& description)
|
||||
QString RiuTabbedTextDialog::description() const
|
||||
{
|
||||
m_description = description;
|
||||
if (m_textProvider && m_textProvider->isValid())
|
||||
{
|
||||
return m_textProvider->description();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Data Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RiuShowTabbedPlotDataDialog::description() const
|
||||
void RiuTabbedTextDialog::redrawText()
|
||||
{
|
||||
if (m_description.isEmpty()) return "Plot Data";
|
||||
return m_description;
|
||||
auto textEdit = currentTextEdit();
|
||||
auto currIndex = m_tabWidget->currentIndex();
|
||||
|
||||
textEdit->setPlainText("Populating Text View...");
|
||||
textEdit->repaint();
|
||||
|
||||
if (currIndex < (int)m_tabTexts.size())
|
||||
{
|
||||
if (m_tabTexts[currIndex].isEmpty())
|
||||
{
|
||||
updateTabText();
|
||||
}
|
||||
textEdit->setPlainText(m_tabTexts[currIndex]);
|
||||
textEdit->repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuShowTabbedPlotDataDialog::setTextProvider(std::function<QString(DateTimePeriod)> textProvider)
|
||||
{
|
||||
m_textProvider = textProvider;
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiuQPlainTextEdit * RiuShowTabbedPlotDataDialog::currentTextEdit() const
|
||||
RiuQPlainTextEdit * RiuTabbedTextDialog::currentTextEdit() const
|
||||
{
|
||||
return dynamic_cast<RiuQPlainTextEdit*>(m_tabWidget->currentWidget());
|
||||
}
|
||||
@ -262,44 +273,32 @@ RiuQPlainTextEdit * RiuShowTabbedPlotDataDialog::currentTextEdit() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
DateTimePeriod RiuShowTabbedPlotDataDialog::indexToPeriod(int index)
|
||||
{
|
||||
auto currTabTitle = m_tabWidget->tabText(index);
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_DAY_NAME)) return DateTimePeriod::DAY;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_WEEK_NAME)) return DateTimePeriod::WEEK;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_MONTH_NAME)) return DateTimePeriod::MONTH;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_QUARTER_NAME)) return DateTimePeriod::QUARTER;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_HALFYEAR_NAME)) return DateTimePeriod::HALFYEAR;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_YEAR_NAME)) return DateTimePeriod::YEAR;
|
||||
if (currTabTitle.contains(RiaQDateTimeTools::TIMESPAN_DECADE_NAME)) return DateTimePeriod::DECADE;
|
||||
return DateTimePeriod::NONE;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuShowTabbedPlotDataDialog::updateText()
|
||||
{
|
||||
auto textEdit = currentTextEdit();
|
||||
void RiuTabbedTextDialog::updateTabText()
|
||||
{
|
||||
auto currIndex = m_tabWidget->currentIndex();
|
||||
if (textEdit && textEdit->toPlainText().isEmpty() && m_textProvider)
|
||||
if (m_textProvider && m_textProvider->isValid() &&
|
||||
m_tabWidget->tabText(currIndex) == m_textProvider->tabTitle(currIndex))
|
||||
{
|
||||
textEdit->setPlainText(m_textProvider(indexToPeriod(currIndex)));
|
||||
m_tabTexts[currIndex] = m_textProvider->tabText(currIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tabTexts[currIndex] = "Data Source No Longer Valid";
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuShowTabbedPlotDataDialog::slotTabChanged(int index)
|
||||
void RiuTabbedTextDialog::slotTabChanged(int index)
|
||||
{
|
||||
updateText();
|
||||
redrawText();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuShowTabbedPlotDataDialog::contextMenuEvent(QContextMenuEvent* event)
|
||||
void RiuTabbedTextDialog::contextMenuEvent(QContextMenuEvent* event)
|
||||
{
|
||||
QMenu menu;
|
||||
RiuQPlainTextEdit* textEdit = dynamic_cast<RiuQPlainTextEdit*>(m_tabWidget->currentWidget());
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPointer>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@ -67,28 +68,38 @@ protected:
|
||||
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class RiuShowTabbedPlotDataDialog : public QDialog
|
||||
class RiuTabbedTextProvider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RiuShowTabbedPlotDataDialog(QWidget* parent = nullptr);
|
||||
virtual bool isValid() const = 0;
|
||||
virtual QString description() const = 0;
|
||||
virtual QString tabTitle(int tabIndex) const = 0;
|
||||
virtual QString tabText(int tabIndex) const = 0;
|
||||
virtual int tabCount() const = 0;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class RiuTabbedTextDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RiuTabbedTextDialog(RiuTabbedTextProvider* textProvider, QWidget* parent = nullptr);
|
||||
|
||||
void setDescription(const QString& description);
|
||||
QString description() const;
|
||||
void setTextProvider(std::function<QString (DateTimePeriod)> textProvider);
|
||||
void redrawText();
|
||||
|
||||
private:
|
||||
RiuQPlainTextEdit * currentTextEdit() const;
|
||||
DateTimePeriod indexToPeriod(int index);
|
||||
void updateText();
|
||||
RiuQPlainTextEdit* currentTextEdit() const;
|
||||
void updateTabText();
|
||||
|
||||
QTabWidget* m_tabWidget;
|
||||
QString m_description;
|
||||
std::function<QString(DateTimePeriod)> m_textProvider;
|
||||
QTabWidget* m_tabWidget;
|
||||
QPointer<RiuTabbedTextProvider> m_textProvider;
|
||||
std::vector<QString> m_tabTexts;
|
||||
|
||||
private slots:
|
||||
void slotTabChanged(int index);
|
||||
|
Loading…
Reference in New Issue
Block a user