...
 
Commits (3)
......@@ -121,6 +121,9 @@ bool Application::initialize()
// Register our floating-point data type with the Qt type system.
qRegisterMetaType<FloatType>("FloatType");
// Register generic object reference type with the Qt type system.
qRegisterMetaType<OORef<OvitoObject>>("OORef<OvitoObject>");
// Register Qt stream operators for basic types.
qRegisterMetaTypeStreamOperators<Vector2>("Ovito::Vector2");
qRegisterMetaTypeStreamOperators<Vector3>("Ovito::Vector3");
......
......@@ -109,16 +109,16 @@ void ModifierTemplatesPage::onCreateTemplate()
QVector<OORef<Modifier>> modifierList;
for(int index = 0; index < pipelineModel->rowCount(); index++) {
PipelineListItem* item = pipelineModel->item(index);
Modifier* modifier = dynamic_object_cast<Modifier>(item->object());
if(modifier) {
QListWidgetItem* listItem = new QListWidgetItem(modifier->objectTitle(), modifierListWidget);
ModifierApplication* modApp = dynamic_object_cast<ModifierApplication>(item->object());
if(modApp && modApp->modifier()) {
QListWidgetItem* listItem = new QListWidgetItem(modApp->modifier()->objectTitle(), modifierListWidget);
listItem->setFlags(Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren));
if(pipelineModel->selectedItem() == item) {
selectedModifier = modifier;
selectedModifier = modApp->modifier();
listItem->setCheckState(Qt::Checked);
}
else listItem->setCheckState(Qt::Unchecked);
modifierList.push_back(modifier);
modifierList.push_back(modApp->modifier());
}
}
if(modifierList.empty())
......
......@@ -244,7 +244,8 @@ void PipelineListModel::applyModifiers(const QVector<OORef<Modifier>>& modifiers
currentItem = currentItem->parent();
}
if(OORef<PipelineObject> pobj = dynamic_object_cast<PipelineObject>(currentItem->object())) {
for(Modifier* modifier : modifiers) {
for(int i = modifiers.size() - 1; i >= 0; i--) {
Modifier* modifier = modifiers[i];
auto dependentsList = pobj->dependents();
OORef<ModifierApplication> modApp = modifier->createModifierApplication();
modApp->setModifier(modifier);
......
......@@ -78,7 +78,7 @@ protected Q_SLOTS:
protected:
virtual void mouseReleaseEvent(QMouseEvent* event) override {
if(event->button() == Qt::LeftButton) {
if(event->button() == Qt::LeftButton && event->y() < _tabBar->height()) {
onTabBarClicked(-1);
event->accept();
}
......
......@@ -56,11 +56,11 @@ void TextLabelOverlayEditor::createUI(const RolloutInsertionParameters& rolloutP
layout->setColumnStretch(1, 3);
layout->setColumnStretch(2, 1);
// This widget displays the list of available ObjectNodes in the current scene.
class ObjectNodeComboBox : public QComboBox {
// This widget displays the list of available pipeline nodes in the current scene.
class PipelineSceneNodeComboBox : public QComboBox {
public:
/// Initializes the widget.
ObjectNodeComboBox(QWidget* parent = nullptr) : QComboBox(parent), _overlay(nullptr) {}
PipelineSceneNodeComboBox(QWidget* parent = nullptr) : QComboBox(parent), _overlay(nullptr) {}
/// Sets the overlay being edited.
void setOverlay(TextLabelOverlay* overlay) { _overlay = overlay; }
......@@ -84,7 +84,7 @@ void TextLabelOverlayEditor::createUI(const RolloutInsertionParameters& rolloutP
TextLabelOverlay* _overlay;
};
ObjectNodeComboBox* nodeComboBox = new ObjectNodeComboBox();
PipelineSceneNodeComboBox* nodeComboBox = new PipelineSceneNodeComboBox();
CustomParameterUI* sourcePUI = new CustomParameterUI(this, "sourceNode", nodeComboBox,
[nodeComboBox](const QVariant& value) {
nodeComboBox->clear();
......@@ -176,34 +176,35 @@ void TextLabelOverlayEditor::createUI(const RolloutInsertionParameters& rolloutP
******************************************************************************/
bool TextLabelOverlayEditor::referenceEvent(RefTarget* source, const ReferenceEvent& event)
{
if(source == editObject() && event.type() == ReferenceEvent::TargetChanged) {
updateEditorFields();
if(source == editObject() && (event.type() == ReferenceEvent::TargetChanged || event.type() == ReferenceEvent::PreliminaryStateAvailable)) {
updateEditorFieldsLater(this);
}
return PropertiesEditor::referenceEvent(source, event);
}
/******************************************************************************
* Updates the enabled/disabled status of the editor's controls.
* Updates the UI.
******************************************************************************/
void TextLabelOverlayEditor::updateEditorFields()
{
TextLabelOverlay* overlay = static_object_cast<TextLabelOverlay>(editObject());
if(!overlay) return;
QString str;
QStringList variableNames;
if(PipelineSceneNode* node = overlay->sourceNode()) {
const PipelineFlowState& flowState = node->evaluatePipelinePreliminary(false);
str.append(tr("<p>Dynamic variables that can be referenced in the label text:</b><ul>"));
for(const QString& attrName : flowState.attributes().keys()) {
str.append(QStringLiteral("<li>[%1]</li>").arg(attrName.toHtmlEscaped()));
variableNames.push_back(QStringLiteral("[") + attrName + QStringLiteral("]"));
if(TextLabelOverlay* overlay = static_object_cast<TextLabelOverlay>(editObject())) {
if(PipelineSceneNode* node = overlay->sourceNode()) {
const PipelineFlowState& flowState = node->evaluatePipelinePreliminary(false);
str.append(tr("<p>Dynamic attributes that can be referenced in the label text:</b><ul>"));
for(const QString& attrName : flowState.attributes().keys()) {
str.append(QStringLiteral("<li>[%1]</li>").arg(attrName.toHtmlEscaped()));
variableNames.push_back(QStringLiteral("[") + attrName + QStringLiteral("]"));
}
str.append(QStringLiteral("</ul></p><p></p>"));
}
str.append(QStringLiteral("</ul></p><p></p>"));
}
_attributeNamesList->setText(str);
_attributeNamesList->updateGeometry();
_textEdit->setWordList(variableNames);
container()->updateRolloutsLater();
}
OVITO_END_INLINE_NAMESPACE
......
......@@ -24,6 +24,7 @@
#include <gui/GUI.h>
#include <gui/properties/PropertiesEditor.h>
#include <core/utilities/DeferredMethodInvocation.h>
namespace Ovito { OVITO_BEGIN_INLINE_NAMESPACE(View) OVITO_BEGIN_INLINE_NAMESPACE(Internal)
......@@ -50,17 +51,16 @@ protected:
protected Q_SLOTS:
/// Updates the enabled/disabled status of the editor's controls.
/// Updates the UI.
void updateEditorFields();
private:
QLabel* _attributeNamesList;
AutocompleteTextEdit* _textEdit;
DeferredMethodInvocation<TextLabelOverlayEditor, &TextLabelOverlayEditor::updateEditorFields> updateEditorFieldsLater;
};
OVITO_END_INLINE_NAMESPACE
OVITO_END_INLINE_NAMESPACE
} // End of namespace
......@@ -173,13 +173,14 @@ bool BinAndReduceModifierEditor::referenceEvent(RefTarget* source, const Referen
void BinAndReduceModifierEditor::plotData()
{
BinAndReduceModifier* modifier = static_object_cast<BinAndReduceModifier>(editObject());
if(!modifier)
BinAndReduceModifierApplication* modApp = dynamic_object_cast<BinAndReduceModifierApplication>(someModifierApplication());
if(!modifier || !modApp || !modifier->isEnabled())
return;
int binDataSizeX = std::max(1, modifier->numberOfBinsX());
int binDataSizeY = std::max(1, modifier->numberOfBinsY());
if(modifier->is1D()) binDataSizeY = 1;
size_t binDataSize = binDataSizeX * binDataSizeY;
size_t binDataSize = (size_t)binDataSizeX * (size_t)binDataSizeY;
if(modifier->is1D()) {
_plot->setAxisTitle(QwtPlot::yRight, QString());
......@@ -205,20 +206,20 @@ void BinAndReduceModifierEditor::plotData()
if(_plotRaster) _plotRaster->hide();
if(modifier->binData().empty()) {
if(!modApp->binData() || modApp->binData()->size() != binDataSize) {
_plotCurve->hide();
return;
}
_plotCurve->show();
QVector<QPointF> plotData(binDataSize + 1);
double binSize = (modifier->xAxisRangeEnd() - modifier->xAxisRangeStart()) / binDataSize;
double binSize = (modApp->range1().second - modApp->range1().first) / binDataSize;
for(int i = 1; i <= binDataSize; i++) {
plotData[i].rx() = binSize * i + modifier->xAxisRangeStart();
plotData[i].ry() = modifier->binData()[i-1];
plotData[i].rx() = binSize * i + modApp->range1().first;
plotData[i].ry() = modApp->binData()->getFloat(i-1);
}
plotData.front().rx() = modifier->xAxisRangeStart();
plotData.front().ry() = modifier->binData().front();
plotData.front().rx() = modApp->range1().first;
plotData.front().ry() = modApp->binData()->getFloat(0);
_plotCurve->setSamples(plotData);
_plot->setAxisAutoScale(QwtPlot::xBottom);
......@@ -257,19 +258,21 @@ void BinAndReduceModifierEditor::plotData()
_plot->plotLayout()->setAlignCanvasToScales(true);
}
if(modifier->binData().empty()) {
if(!modApp->binData() || modApp->binData()->size() != binDataSize) {
_plotRaster->hide();
return;
}
_plotRaster->show();
_plot->enableAxis(QwtPlot::yRight);
_rasterData->setValueMatrix(modifier->binData(), binDataSizeX);
_rasterData->setInterval(Qt::XAxis, QwtInterval(modifier->xAxisRangeStart(), modifier->xAxisRangeEnd(), QwtInterval::ExcludeMaximum));
_rasterData->setInterval(Qt::YAxis, QwtInterval(modifier->yAxisRangeStart(), modifier->yAxisRangeEnd(), QwtInterval::ExcludeMaximum));
QVector<double> values(modApp->binData()->size());
std::copy(modApp->binData()->constDataFloat(), modApp->binData()->constDataFloat() + modApp->binData()->size(), values.begin());
_rasterData->setValueMatrix(values, binDataSizeX);
_rasterData->setInterval(Qt::XAxis, QwtInterval(modApp->range1().first, modApp->range1().second, QwtInterval::ExcludeMaximum));
_rasterData->setInterval(Qt::YAxis, QwtInterval(modApp->range2().first, modApp->range2().second, QwtInterval::ExcludeMaximum));
QwtInterval zInterval;
if(!modifier->fixPropertyAxisRange()) {
auto minmax = std::minmax_element(modifier->binData().begin(), modifier->binData().end());
auto minmax = std::minmax_element(modApp->binData()->constDataFloat(), modApp->binData()->constDataFloat() + modApp->binData()->size());
zInterval = QwtInterval(*minmax.first, *minmax.second, QwtInterval::ExcludeMaximum);
}
else {
......@@ -277,8 +280,8 @@ void BinAndReduceModifierEditor::plotData()
}
_plot->axisScaleEngine(QwtPlot::yRight)->setAttribute(QwtScaleEngine::Inverted, zInterval.minValue() > zInterval.maxValue());
_rasterData->setInterval(Qt::ZAxis, zInterval.normalized());
_plot->setAxisScale(QwtPlot::xBottom, modifier->xAxisRangeStart(), modifier->xAxisRangeEnd());
_plot->setAxisScale(QwtPlot::yLeft, modifier->yAxisRangeStart(), modifier->yAxisRangeEnd());
_plot->setAxisScale(QwtPlot::xBottom, modApp->range1().first, modApp->range1().second);
_plot->setAxisScale(QwtPlot::yLeft, modApp->range2().first, modApp->range2().second);
_plot->axisWidget(QwtPlot::yRight)->setColorMap(zInterval.normalized(), new ColorMap());
_plot->setAxisScale(QwtPlot::yRight, zInterval.minValue(), zInterval.maxValue());
_plot->setAxisTitle(QwtPlot::yRight, modifier->sourceProperty().name());
......@@ -307,10 +310,8 @@ void BinAndReduceModifierEditor::updateWidgets()
void BinAndReduceModifierEditor::onSaveData()
{
BinAndReduceModifier* modifier = static_object_cast<BinAndReduceModifier>(editObject());
if(!modifier)
return;
if(modifier->binData().empty())
BinAndReduceModifierApplication* modApp = dynamic_object_cast<BinAndReduceModifierApplication>(someModifierApplication());
if(!modifier || !modApp)
return;
QString fileName = QFileDialog::getSaveFileName(mainWindow(),
......@@ -327,24 +328,28 @@ void BinAndReduceModifierEditor::onSaveData()
int binDataSizeX = std::max(1, modifier->numberOfBinsX());
int binDataSizeY = std::max(1, modifier->numberOfBinsY());
if (modifier->is1D()) binDataSizeY = 1;
FloatType binSizeX = (modifier->xAxisRangeEnd() - modifier->xAxisRangeStart()) / binDataSizeX;
FloatType binSizeY = (modifier->yAxisRangeEnd() - modifier->yAxisRangeStart()) / binDataSizeY;
if(modifier->is1D()) binDataSizeY = 1;
FloatType binSizeX = (modApp->range1().second - modApp->range1().first) / binDataSizeX;
FloatType binSizeY = (modApp->range2().second - modApp->range2().first) / binDataSizeY;
if(!modifier->isEnabled() || !modApp->binData() || modApp->binData()->size() != (size_t)binDataSizeX * (size_t)binDataSizeY)
throwException(tr("Modifier has not been evaluated as part of the data pipeline yet."));
QTextStream stream(&file);
if (binDataSizeY == 1) {
stream << "# " << modifier->sourceProperty().nameWithComponent() << " bin size: " << binSizeX << endl;
for(size_t i = 0; i < modifier->binData().size(); i++) {
stream << (binSizeX * (FloatType(i) + 0.5f) + modifier->xAxisRangeStart()) << " " << modifier->binData()[i] << endl;
if(binDataSizeY == 1) {
stream << "# " << modifier->sourceProperty().nameWithComponent() << ", bin size: " << binSizeX << ", bin count: " << binDataSizeX << endl;
for(int i = 0; i < binDataSizeX; i++) {
stream << (binSizeX * (FloatType(i) + FloatType(0.5)) + modApp->range1().first) << " " << modApp->binData()->getFloat(i) << "\n";
}
}
else {
stream << "# " << modifier->sourceProperty().nameWithComponent() << " bin size X: " << binDataSizeX << ", bin size Y: " << binDataSizeY << endl;
stream << "# " << modifier->sourceProperty().nameWithComponent() << ", bin size X: " << binDataSizeX << ", bin count X: " << binDataSizeX << ", bin size Y: " << binDataSizeY << ", bin count Y: " << binDataSizeY << endl;
auto d = modApp->binData()->constDataFloat();
for(int i = 0; i < binDataSizeY; i++) {
for(int j = 0; j < binDataSizeX; j++) {
stream << modifier->binData()[i*binDataSizeX+j] << " ";
stream << *d++ << " ";
}
stream << endl;
stream << "\n";
}
}
}
......
......@@ -106,7 +106,7 @@ void CoordinationNumberModifierEditor::plotRDF()
if(!modApp || !modifier)
return;
if(modApp->rdfX().empty())
if(!modApp->rdfX() || !modApp->rdfY())
return;
if(!_plotCurve) {
......@@ -118,14 +118,18 @@ void CoordinationNumberModifierEditor::plotRDF()
plotGrid->setPen(Qt::gray, 0, Qt::DotLine);
plotGrid->attach(_rdfPlot);
}
_plotCurve->setSamples(modApp->rdfX().data(), modApp->rdfY().data(), modApp->rdfX().size());
QVector<double> x(modApp->rdfX()->size());
QVector<double> y(modApp->rdfY()->size());
if(modApp->rdfX()->copyTo(x.begin()) && modApp->rdfY()->copyTo(y.begin()))
_plotCurve->setSamples(x, y);
// Determine lower X bound where the histogram is non-zero.
_rdfPlot->setAxisAutoScale(QwtPlot::xBottom);
double maxx = modifier->cutoff();
for(int i = 0; i < modApp->rdfX().size(); i++) {
if(modApp->rdfY()[i] != 0) {
double minx = std::floor(modApp->rdfX()[i] * 9.0 / maxx) / 10.0 * maxx;
for(int i = 0; i < x.size(); i++) {
if(y[i] != 0) {
double minx = std::floor(x[i] * 9.0 / maxx) / 10.0 * maxx;
_rdfPlot->setAxisScale(QwtPlot::xBottom, minx, maxx);
break;
}
......@@ -143,7 +147,7 @@ void CoordinationNumberModifierEditor::onSaveData()
if(!modApp)
return;
if(modApp->rdfX().empty())
if(!modApp->rdfX() || !modApp->rdfY() || modApp->rdfY()->size() != modApp->rdfX()->size())
return;
QString fileName = QFileDialog::getSaveFileName(mainWindow(),
......@@ -162,8 +166,8 @@ void CoordinationNumberModifierEditor::onSaveData()
stream << "# 1: Bin number" << endl;
stream << "# 2: r" << endl;
stream << "# 3: g(r)" << endl;
for(int i = 0; i < modApp->rdfX().size(); i++) {
stream << i << "\t" << modApp->rdfX()[i] << "\t" << modApp->rdfY()[i] << endl;
for(size_t i = 0; i < modApp->rdfX()->size(); i++) {
stream << i << "\t" << modApp->rdfX()->getFloat(i) << "\t" << modApp->rdfY()->getFloat(i) << endl;
}
}
catch(const Exception& ex) {
......
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2014) Alexander Stukowski
// Copyright (2018) Alexander Stukowski
// Copyright (2014) Lars Pastewka
//
// This file is part of OVITO (Open Visualization Tool).
......@@ -26,6 +26,7 @@
#include <plugins/particles/Particles.h>
#include <plugins/stdobj/properties/PropertyStorage.h>
#include <plugins/particles/objects/ParticleProperty.h>
#include <core/dataset/pipeline/ModifierApplication.h>
#include <core/dataset/pipeline/Modifier.h>
namespace Ovito { namespace Particles { OVITO_BEGIN_INLINE_NAMESPACE(Modifiers) OVITO_BEGIN_INLINE_NAMESPACE(Analysis)
......@@ -70,27 +71,12 @@ public:
/// \brief Modifies the input data.
virtual Future<PipelineFlowState> evaluate(TimePoint time, ModifierApplication* modApp, const PipelineFlowState& input) override;
/// Returns the stored average data.
const QVector<double>& binData() const { return _binData; }
/// Set start and end value of the plotting property axis.
void setPropertyAxisRange(FloatType start, FloatType end) {
setPropertyAxisRangeStart(start);
setPropertyAxisRangeEnd(end);
}
/// Returns the start value of the plotting x-axis.
FloatType xAxisRangeStart() const { return _xAxisRangeStart; }
/// Returns the end value of the plotting x-axis.
FloatType xAxisRangeEnd() const { return _xAxisRangeEnd; }
/// Returns the start value of the plotting y-axis.
FloatType yAxisRangeStart() const { return _yAxisRangeStart; }
/// Returns the end value of the plotting y-axis.
FloatType yAxisRangeEnd() const { return _yAxisRangeEnd; }
/// Returns true if binning in a single direction only.
bool is1D() {
return bin1D(_binDirection);
......@@ -142,22 +128,34 @@ private:
/// Controls whether the modifier should take into account only selected particles.
DECLARE_MODIFIABLE_PROPERTY_FIELD(bool, onlySelected, setOnlySelected);
};
/**
* \brief The type of ModifierApplication create for a BinAndReduceModifier
* when it is inserted into in a data pipeline.
*/
class OVITO_PARTICLES_EXPORT BinAndReduceModifierApplication : public ModifierApplication
{
OVITO_CLASS(BinAndReduceModifierApplication)
Q_OBJECT
public:
/// Stores the start value of the plotting x-axis.
FloatType _xAxisRangeStart;
/// Constructor.
Q_INVOKABLE BinAndReduceModifierApplication(DataSet* dataset) : ModifierApplication(dataset) {}
/// Stores the end value of the plotting x-axis.
FloatType _xAxisRangeEnd;
using Interval = std::pair<FloatType,FloatType>;
private:
/// Stores the start value of the plotting y-axis.
FloatType _yAxisRangeStart;
/// The 1-dimensional bin values.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(PropertyPtr, binData, setBinData, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
/// Stores the end value of the plotting y-axis.
FloatType _yAxisRangeEnd;
/// The interval along the first axis.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(Interval, range1, setRange1, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
/// Stores the averaged data.
/// Use double precision numbers to avoid precision loss when adding up a large number of values.
QVector<double> _binData;
/// The interval along the second axis.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(Interval, range2, setRange2, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
};
OVITO_END_INLINE_NAMESPACE
......
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2014) Alexander Stukowski
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
......@@ -25,6 +25,7 @@
#include <core/app/Application.h>
#include <core/dataset/DataSet.h>
#include <plugins/stdobj/simcell/SimulationCellObject.h>
#include <plugins/stdobj/plot/PlotObject.h>
#include <core/dataset/pipeline/ModifierApplication.h>
#include <core/utilities/units/UnitsManager.h>
#include "CoordinationNumberModifier.h"
......@@ -34,14 +35,19 @@
namespace Ovito { namespace Particles { OVITO_BEGIN_INLINE_NAMESPACE(Modifiers) OVITO_BEGIN_INLINE_NAMESPACE(Analysis)
IMPLEMENT_OVITO_CLASS(CoordinationNumberModifier);
IMPLEMENT_OVITO_CLASS(CoordinationNumberModifierApplication);
DEFINE_PROPERTY_FIELD(CoordinationNumberModifier, cutoff);
DEFINE_PROPERTY_FIELD(CoordinationNumberModifier, numberOfBins);
SET_PROPERTY_FIELD_LABEL(CoordinationNumberModifier, cutoff, "Cutoff radius");
SET_PROPERTY_FIELD_LABEL(CoordinationNumberModifier, numberOfBins, "Number of histogram bins");
SET_PROPERTY_FIELD_UNITS_AND_MINIMUM(CoordinationNumberModifier, cutoff, WorldParameterUnit, 0);
SET_PROPERTY_FIELD_UNITS_AND_RANGE(CoordinationNumberModifier, numberOfBins, IntegerParameterUnit, 4, 100000);
IMPLEMENT_OVITO_CLASS(CoordinationNumberModifierApplication);
SET_MODIFIER_APPLICATION_TYPE(CoordinationNumberModifier, CoordinationNumberModifierApplication);
DEFINE_PROPERTY_FIELD(CoordinationNumberModifierApplication, rdfX);
DEFINE_PROPERTY_FIELD(CoordinationNumberModifierApplication, rdfY);
SET_PROPERTY_FIELD_CHANGE_EVENT(CoordinationNumberModifierApplication, rdfX, ReferenceEvent::ObjectStatusChanged);
SET_PROPERTY_FIELD_CHANGE_EVENT(CoordinationNumberModifierApplication, rdfY, ReferenceEvent::ObjectStatusChanged);
/******************************************************************************
* Constructs the modifier object.
......@@ -75,7 +81,7 @@ Future<AsynchronousModifier::ComputeEnginePtr> CoordinationNumberModifier::creat
// The number of sampling intervals for the radial distribution function.
int rdfSampleCount = std::max(numberOfBins(), 4);
if(rdfSampleCount > 100000)
throwException(tr("Number of histogram bins is too large."));
throwException(tr("Requested number of histogram bins is too large. Limit is 100,000 histogram bins."));
// Create engine object. Pass all relevant modifier parameters to the engine as well as the input data.
return std::make_shared<CoordinationAnalysisEngine>(posProperty->storage(), inputCell->data(), cutoff(), rdfSampleCount);
......@@ -97,8 +103,6 @@ void CoordinationNumberModifier::CoordinationAnalysisEngine::perform()
setProgressValue(0);
setProgressMaximum(particleCount / 1000);
QVector<double> rdfHistogram(_rdfSampleCount, 0.0);
// Perform analysis on each particle in parallel.
std::vector<QFuture<void>> workers;
size_t num_threads = Application::instance()->idealThreadCount();
......@@ -110,8 +114,8 @@ void CoordinationNumberModifier::CoordinationAnalysisEngine::perform()
if(t == num_threads - 1) {
endIndex += particleCount % num_threads;
}
workers.push_back(QtConcurrent::run([&neighborListBuilder, startIndex, endIndex, &mutex, &rdfHistogram, this]() {
FloatType rdfBinSize = (_cutoff + FLOATTYPE_EPSILON) / _rdfSampleCount;
workers.push_back(QtConcurrent::run([&neighborListBuilder, startIndex, endIndex, &mutex, this]() {
FloatType rdfBinSize = _cutoff / _rdfSampleCount;
std::vector<double> threadLocalRDF(_rdfSampleCount, 0);
int* coordOutput = _results->coordinationNumbers()->dataInt() + startIndex;
for(size_t i = startIndex; i < endIndex; ++coordOutput) {
......@@ -134,9 +138,10 @@ void CoordinationNumberModifier::CoordinationAnalysisEngine::perform()
return;
}
std::lock_guard<std::mutex> lock(mutex);
auto iter_out = rdfHistogram.begin();
for(auto iter = threadLocalRDF.cbegin(); iter != threadLocalRDF.cend(); ++iter, ++iter_out)
*iter_out += *iter;
// Combine RDF histograms.
auto bin = _results->rdfY()->dataFloat();
for(auto iter = threadLocalRDF.cbegin(); iter != threadLocalRDF.cend(); ++iter)
*bin++ += *iter;
}));
startIndex = endIndex;
endIndex += chunkSize;
......@@ -146,28 +151,30 @@ void CoordinationNumberModifier::CoordinationAnalysisEngine::perform()
t.waitForFinished();
// Normalize RDF.
_results->rdfX().resize(_rdfSampleCount);
_results->rdfY().resize(_rdfSampleCount);
if(!cell().is2D()) {
double rho = positions()->size() / cell().volume3D();
double constant = 4.0/3.0 * FLOATTYPE_PI * rho * positions()->size();
double stepSize = cutoff() / _rdfSampleCount;
for(int i = 0; i < _rdfSampleCount; i++) {
auto rdfX = _results->rdfX()->dataFloat();
auto rdfY = _results->rdfY()->dataFloat();
for(int i = 0; i < _rdfSampleCount; i++, ++rdfX, ++rdfY) {
double r = stepSize * i;
double r2 = r + stepSize;
_results->rdfX()[i] = r + 0.5 * stepSize;
_results->rdfY()[i] = rdfHistogram[i] / (constant * (r2*r2*r2 - r*r*r));
*rdfX = r + 0.5 * stepSize;
*rdfY /= constant * (r2*r2*r2 - r*r*r);
}
}
else {
double rho = positions()->size() / cell().volume2D();
double constant = FLOATTYPE_PI * rho * positions()->size();
double stepSize = cutoff() / _rdfSampleCount;
for(int i = 0; i < _rdfSampleCount; i++) {
auto rdfX = _results->rdfX()->dataFloat();
auto rdfY = _results->rdfY()->dataFloat();
for(int i = 0; i < _rdfSampleCount; i++, ++rdfX, ++rdfY) {
double r = stepSize * i;
double r2 = r + stepSize;
_results->rdfX()[i] = r + 0.5 * stepSize;
_results->rdfY()[i] = rdfHistogram[i] / (constant * (r2*r2 - r*r));
*rdfX = r + 0.5 * stepSize;
*rdfY /= constant * (r2*r2 - r*r);
}
}
......@@ -183,11 +190,19 @@ PipelineFlowState CoordinationNumberModifier::CoordinationAnalysisResults::apply
PipelineFlowState output = input;
ParticleOutputHelper poh(modApp->dataset(), output);
if(coordinationNumbers()->size() != poh.outputParticleCount())
modApp->throwException(tr("Cached modifier results are obsolete, because the number of input particles has changed."));
modApp->throwException(tr("Cached modifier results became obsolete, because the number of input particles has changed."));
poh.outputProperty<ParticleProperty>(coordinationNumbers());
// Store the RDF data points in the ModifierApplication.
static_object_cast<CoordinationNumberModifierApplication>(modApp)->setRDF(rdfX(), rdfY());
// Output RDF histogram.
OORef<PlotObject> rdfPlotObj = new PlotObject(modApp->dataset());
rdfPlotObj->setx(rdfX());
rdfPlotObj->sety(rdfY());
rdfPlotObj->setTitle(tr("RDF"));
output.addObject(rdfPlotObj);
// Store the RDF data points in the ModifierApplication in order to display the RDF in the modifier's UI panel.
static_object_cast<CoordinationNumberModifierApplication>(modApp)->setRdfX(rdfX());
static_object_cast<CoordinationNumberModifierApplication>(modApp)->setRdfY(rdfY());
return output;
}
......
......@@ -26,6 +26,7 @@
#include <plugins/particles/util/CutoffNeighborFinder.h>
#include <plugins/particles/objects/ParticleProperty.h>
#include <plugins/stdobj/simcell/SimulationCell.h>
#include <plugins/stdobj/properties/PropertyStorage.h>
#include <core/dataset/pipeline/AsynchronousModifier.h>
#include <core/dataset/pipeline/AsynchronousModifierApplication.h>
......@@ -72,8 +73,10 @@ private:
public:
/// Constructor.
CoordinationAnalysisResults(size_t particleCount) :
_coordinationNumbers(ParticleProperty::createStandardStorage(particleCount, ParticleProperty::CoordinationProperty, true)) {}
CoordinationAnalysisResults(size_t particleCount, size_t rdfSampleCount) :
_coordinationNumbers(ParticleProperty::createStandardStorage(particleCount, ParticleProperty::CoordinationProperty, true)),
_rdfX(std::make_shared<PropertyStorage>(rdfSampleCount, PropertyStorage::Float, 1, 0, tr("Pair separation distance"), false)),
_rdfY(std::make_shared<PropertyStorage>(rdfSampleCount, PropertyStorage::Float, 1, 0, tr("g(r)"), true)) {}
/// Injects the computed results into the data pipeline.
virtual PipelineFlowState apply(TimePoint time, ModifierApplication* modApp, const PipelineFlowState& input) override;
......@@ -81,17 +84,17 @@ private:
/// Returns the property storage that contains the computed coordination numbers.
const PropertyPtr& coordinationNumbers() const { return _coordinationNumbers; }
/// Returns the X coordinates of the RDF data points.
QVector<double>& rdfX() { return _rdfX; }
/// Returns the property storage containing the x-coordinates of the data points of the RDF histogram.
const PropertyPtr& rdfX() const { return _rdfX; }
/// Returns the Y coordinates of the RDF data points.
QVector<double>& rdfY() { return _rdfY; }
/// Returns the property storage containing the y-coordinates of the data points of the RDF histogram.
const PropertyPtr& rdfY() const { return _rdfY; }
private:
const PropertyPtr _coordinationNumbers;
QVector<double> _rdfX;
QVector<double> _rdfY;
const PropertyPtr _rdfX;
const PropertyPtr _rdfY;
};
/// Computes the modifier's results.
......@@ -105,7 +108,7 @@ private:
_simCell(simCell),
_cutoff(cutoff),
_rdfSampleCount(rdfSampleCount),
_results(std::make_shared<CoordinationAnalysisResults>(positions->size())) {}
_results(std::make_shared<CoordinationAnalysisResults>(positions->size(), rdfSampleCount)) {}
/// Computes the modifier's results.
virtual void perform() override;
......@@ -152,31 +155,16 @@ public:
/// Constructor.
Q_INVOKABLE CoordinationNumberModifierApplication(DataSet* dataset) : AsynchronousModifierApplication(dataset) {}
/// Returns the X coordinates of the RDF data points.
const QVector<double>& rdfX() const { return _rdfX; }
/// Returns the Y coordinates of the RDF data points.
const QVector<double>& rdfY() const { return _rdfY; }
/// Sets the stored RDF data points.
void setRDF(QVector<double> x, QVector<double> y) {
_rdfX = std::move(x);
_rdfY = std::move(y);
notifyDependents(ReferenceEvent::ObjectStatusChanged);
}
private:
/// The X coordinates of the RDF data points.
QVector<double> _rdfX;
/// The Y coordinates of the RDF data points.
QVector<double> _rdfY;
/// The x-coordinates of the RDF histogram points.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(PropertyPtr, rdfX, setRdfX, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
/// The y-coordinates of the RDF histogram points.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(PropertyPtr, rdfY, setRdfY, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
};
OVITO_END_INLINE_NAMESPACE
OVITO_END_INLINE_NAMESPACE
} // End of namespace
} // End of namespace
......@@ -9,14 +9,13 @@ import ovito.modifiers
from ovito.plugins.Particles import CoordinationNumberModifier
# Implement the 'rdf' attribute of the CoordinationNumberModifier class.
# This is for backward compatibility with OVITO 2.9.0:
def _CoordinationNumberModifier_rdf(self):
"""
Returns a NumPy array containing the radial distribution function (RDF) computed by the modifier.
The returned array is two-dimensional and consists of the [*r*, *g(r)*] data points of the tabulated *g(r)* RDF function.
Note that accessing this array is only possible after the modifier has computed its results.
Thus, you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to ensure that this information is up to date, see the example above.
"""
# Returns a NumPy array containing the radial distribution function (RDF) computed by the modifier.
# The returned array is two-dimensional and consists of the [*r*, *g(r)*] data points of the tabulated *g(r)* RDF function.
#
# Note that accessing this array is only possible after the modifier has computed its results.
# Thus, you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to ensure that this information is up to date, see the example above.
return numpy.transpose((self.rdf_x,self.rdf_y))
CoordinationNumberModifier.rdf = property(_CoordinationNumberModifier_rdf)
......@@ -273,14 +273,18 @@ void defineModifiersSubmodule(py::module m)
"\n\n"
":Default: ``False``\n")
.def_property_readonly("bin_data", py::cpp_function([](BinAndReduceModifier& mod) {
BinAndReduceModifierApplication* modApp = dynamic_object_cast<BinAndReduceModifierApplication>(mod.someModifierApplication());
if(!modApp || !modApp->binData()) mod.throwException(BinAndReduceModifier::tr("Modifier has not been evaluated yet. Bin data is not yet available."));
std::vector<size_t> shape;
if(mod.is1D()) shape.push_back(mod.binData().size());
else {
if(mod.is1D() && modApp->binData()->size() == mod.numberOfBinsX()) {
shape.push_back(mod.numberOfBinsX());
}
else if(!mod.is1D() && modApp->binData()->size() == (size_t)mod.numberOfBinsY() * (size_t)mod.numberOfBinsY()) {
shape.push_back(mod.numberOfBinsY());
shape.push_back(mod.numberOfBinsX());
OVITO_ASSERT(shape[0] * shape[1] == mod.binData().size());
}
py::array_t<double> array(shape, mod.binData().data(), py::cast(&mod));
else mod.throwException(BinAndReduceModifier::tr("Modifier has not been evaluated yet. Bin data is not yet available."));
py::array_t<FloatType> array(shape, modApp->binData()->constDataFloat(), py::cast(modApp));
// Mark array as read-only.
reinterpret_cast<py::detail::PyArray_Proxy*>(array.ptr())->flags &= ~py::detail::npy_api::NPY_ARRAY_WRITEABLE_;
return array;
......@@ -292,19 +296,24 @@ void defineModifiersSubmodule(py::module m)
"\n\n"
"Note that accessing this array is only possible after the modifier has computed its results. "
"Thus, you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to ensure that the binning and reduction operation was performed.")
.def_property_readonly("axis_range_x", [](BinAndReduceModifier& modifier) {
return py::make_tuple(modifier.xAxisRangeStart(), modifier.xAxisRangeEnd());
},
"A 2-tuple containing the range of the generated bin grid along the first binning axis. "
"Note that this is an output attribute which is only valid after the modifier has performed the bin and reduce operation. "
"That means you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to evaluate the data pipeline.")
.def_property_readonly("axis_range_y", [](BinAndReduceModifier& modifier) {
return py::make_tuple(modifier.yAxisRangeStart(), modifier.yAxisRangeEnd());
},
"A 2-tuple containing the range of the generated bin grid along the second binning axis. "
"Note that this is an output attribute which is only valid after the modifier has performed the bin and reduce operation. "
"That means you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to evaluate the data pipeline.")
.def_property_readonly("axis_range_x", [](BinAndReduceModifier& mod) {
BinAndReduceModifierApplication* modApp = dynamic_object_cast<BinAndReduceModifierApplication>(mod.someModifierApplication());
if(!modApp || !modApp->binData()) mod.throwException(BinAndReduceModifier::tr("Modifier has not been evaluated yet. Bin data is not yet available."));
return py::make_tuple(modApp->range1().first, modApp->range1().second);
},
"A 2-tuple containing the range of the generated bin grid along the first binning axis. "
"Note that this is an output attribute which is only valid after the modifier has performed the bin and reduce operation. "
"That means you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to evaluate the data pipeline.")
.def_property_readonly("axis_range_y", [](BinAndReduceModifier& mod) {
BinAndReduceModifierApplication* modApp = dynamic_object_cast<BinAndReduceModifierApplication>(mod.someModifierApplication());
if(!modApp || !modApp->binData()) mod.throwException(BinAndReduceModifier::tr("Modifier has not been evaluated yet. Bin data is not yet available."));
return py::make_tuple(modApp->range2().first, modApp->range2().second);
},
"A 2-tuple containing the range of the generated bin grid along the second binning axis. "
"Note that this is an output attribute which is only valid after the modifier has performed the bin and reduce operation. "
"That means you have to call :py:meth:`Pipeline.compute() <ovito.pipeline.Pipeline.compute>` first to evaluate the data pipeline.")
;
ovito_class<BinAndReduceModifierApplication, ModifierApplication>{m};
py::enum_<BinAndReduceModifier::ReductionOperationType>(BinAndReduceModifier_py, "Operation")
.value("Mean", BinAndReduceModifier::RED_MEAN)
......@@ -673,15 +682,15 @@ void defineModifiersSubmodule(py::module m)
// For backward compatibility with OVITO 2.9.0:
.def_property_readonly("rdf_x", py::cpp_function([](CoordinationNumberModifier& mod) {
CoordinationNumberModifierApplication* modApp = dynamic_object_cast<CoordinationNumberModifierApplication>(mod.someModifierApplication());
if(!modApp) mod.throwException(CoordinationNumberModifier::tr("Modifier has not been evaluated yet. RDF data is not yet available."));
py::array_t<double> array((size_t)modApp->rdfX().size(), modApp->rdfX().data(), py::cast(static_cast<ModifierApplication*>(modApp)));
if(!modApp || !modApp->rdfX()) mod.throwException(CoordinationNumberModifier::tr("Modifier has not been evaluated yet. RDF data is not yet available."));
py::array_t<FloatType> array((size_t)modApp->rdfX()->size(), modApp->rdfX()->constDataFloat(), py::cast(static_cast<ModifierApplication*>(modApp)));
reinterpret_cast<py::detail::PyArray_Proxy*>(array.ptr())->flags &= ~py::detail::npy_api::NPY_ARRAY_WRITEABLE_;
return array;
}))
.def_property_readonly("rdf_y", py::cpp_function([](CoordinationNumberModifier& mod) {
CoordinationNumberModifierApplication* modApp = dynamic_object_cast<CoordinationNumberModifierApplication>(mod.someModifierApplication());
if(!modApp) mod.throwException(CoordinationNumberModifier::tr("Modifier has not been evaluated yet. RDF data is not yet available."));
py::array_t<double> array((size_t)modApp->rdfY().size(), modApp->rdfY().data(), py::cast(static_cast<ModifierApplication*>(modApp)));
if(!modApp || !modApp->rdfY()) mod.throwException(CoordinationNumberModifier::tr("Modifier has not been evaluated yet. RDF data is not yet available."));
py::array_t<FloatType> array((size_t)modApp->rdfY()->size(), modApp->rdfY()->constDataFloat(), py::cast(static_cast<ModifierApplication*>(modApp)));
reinterpret_cast<py::detail::PyArray_Proxy*>(array.ptr())->flags &= ~py::detail::npy_api::NPY_ARRAY_WRITEABLE_;
return array;
}))
......
......@@ -54,7 +54,7 @@ void PythonScriptModifier::loadUserDefaults()
setScript("from ovito.data import *\n\n"
"def modify(frame, input, output):\n"
"\tprint(\"Input particle properties:\")\n"
"\tfor name in input.particle_properties.keys():\n"
"\tfor name in input.particles.keys():\n"
"\t\tprint(name)\n");
}
......
......@@ -17,6 +17,7 @@ compute neighbor lists to iterate over the bonds of particles.
* :py:class:`SurfaceMesh`
* :py:class:`TrajectoryLines`
* :py:class:`DislocationNetwork`
* :py:class:`PlotData`
**Auxiliary data classes:**
......
......@@ -188,7 +188,7 @@ void HistogramModifierEditor::plotHistogram()
{
HistogramModifier* modifier = static_object_cast<HistogramModifier>(editObject());
HistogramModifierApplication* modApp = dynamic_object_cast<HistogramModifierApplication>(someModifierApplication());
if(!modifier || !modApp || !modifier->isEnabled() || modApp->histogramData().empty()) {
if(!modifier || !modApp || !modifier->isEnabled() || !modApp->binCounts()) {
if(_plotCurve) _plotCurve->hide();
_histogramPlot->replot();
return;
......@@ -198,19 +198,19 @@ void HistogramModifierEditor::plotHistogram()
_histogramPlot->setAxisTitle(QwtPlot::xBottom, axisTitle);
if(modifier->fixXAxisRange() == false) {
modifier->setXAxisRange(modApp->intervalStart(), modApp->intervalEnd());
modifier->setXAxisRange(modApp->histogramInterval().first, modApp->histogramInterval().second);
}
size_t binCount = modApp->histogramData().size();
size_t binCount = modApp->binCounts()->size();
FloatType binSize = (modifier->xAxisRangeEnd() - modifier->xAxisRangeStart()) / binCount;
QVector<QPointF> plotData(binCount);
for(size_t i = 0; i < binCount; i++) {
plotData[i].rx() = binSize * (i + FloatType(0.5)) + modifier->xAxisRangeStart();
plotData[i].ry() = modApp->histogramData()[i];
plotData[i].ry() = modApp->binCounts()->getInt64(i);
}
if(modifier->fixYAxisRange() == false) {
auto minmaxHistogramData = std::minmax_element(modApp->histogramData().begin(), modApp->histogramData().end());
auto minmaxHistogramData = std::minmax_element(modApp->binCounts()->constDataInt64(), modApp->binCounts()->constDataInt64() + modApp->binCounts()->size());
modifier->setYAxisRange(*minmaxHistogramData.first, *minmaxHistogramData.second);
}
......@@ -261,10 +261,7 @@ void HistogramModifierEditor::onSaveData()
{
HistogramModifier* modifier = static_object_cast<HistogramModifier>(editObject());
HistogramModifierApplication* modApp = dynamic_object_cast<HistogramModifierApplication>(someModifierApplication());
if(!modifier || !modApp)
return;
if(modApp->histogramData().empty())
if(!modifier || !modApp || !modApp->binCounts())
return;
QString fileName = QFileDialog::getSaveFileName(mainWindow(),
......@@ -282,11 +279,11 @@ void HistogramModifierEditor::onSaveData()
QString sourceTitle = modifier->sourceProperty().nameWithComponent();
FloatType binSize = (modifier->xAxisRangeEnd() - modifier->xAxisRangeStart()) / modApp->histogramData().size();
FloatType binSize = (modifier->xAxisRangeEnd() - modifier->xAxisRangeStart()) / modApp->binCounts()->size();
stream << "# " << sourceTitle << " histogram (bin size: " << binSize << ")" << endl;
for(int i = 0; i < modApp->histogramData().size(); i++) {
for(size_t i = 0; i < modApp->binCounts()->size(); i++) {
stream << (binSize * (FloatType(i) + FloatType(0.5)) + modifier->xAxisRangeStart()) << " " <<
modApp->histogramData()[i] << endl;
modApp->binCounts()->getInt64(i) << endl;
}
}
catch(const Exception& ex) {
......
......@@ -23,6 +23,7 @@
#include <core/dataset/DataSet.h>
#include <core/dataset/pipeline/ModifierApplication.h>
#include <plugins/stdobj/properties/PropertyObject.h>
#include <plugins/stdobj/plot/PlotObject.h>
#include <plugins/stdobj/util/InputHelper.h>
#include <plugins/stdobj/util/OutputHelper.h>
#include <core/app/PluginManager.h>
......@@ -32,7 +33,6 @@
namespace Ovito { namespace StdMod {
IMPLEMENT_OVITO_CLASS(HistogramModifier);
IMPLEMENT_OVITO_CLASS(HistogramModifierApplication);
DEFINE_PROPERTY_FIELD(HistogramModifier, numberOfBins);
DEFINE_PROPERTY_FIELD(HistogramModifier, selectInRange);
DEFINE_PROPERTY_FIELD(HistogramModifier, selectionRangeStart);
......@@ -58,7 +58,13 @@ SET_PROPERTY_FIELD_LABEL(HistogramModifier, yAxisRangeEnd, "Y-range end");
SET_PROPERTY_FIELD_LABEL(HistogramModifier, sourceProperty, "Source property");
SET_PROPERTY_FIELD_LABEL(HistogramModifier, onlySelected, "Use only selected elements");
SET_PROPERTY_FIELD_UNITS_AND_RANGE(HistogramModifier, numberOfBins, IntegerParameterUnit, 1, 100000);
IMPLEMENT_OVITO_CLASS(HistogramModifierApplication);
SET_MODIFIER_APPLICATION_TYPE(HistogramModifier, HistogramModifierApplication);
DEFINE_PROPERTY_FIELD(HistogramModifierApplication, binCounts);
DEFINE_PROPERTY_FIELD(HistogramModifierApplication, histogramInterval);
SET_PROPERTY_FIELD_CHANGE_EVENT(HistogramModifierApplication, binCounts, ReferenceEvent::ObjectStatusChanged);
SET_PROPERTY_FIELD_CHANGE_EVENT(HistogramModifierApplication, histogramInterval, ReferenceEvent::ObjectStatusChanged);
/******************************************************************************
* Constructs the modifier object.
......@@ -126,7 +132,7 @@ void HistogramModifier::propertyChanged(const PropertyFieldDescriptor& field)
PipelineFlowState HistogramModifier::evaluatePreliminary(TimePoint time, ModifierApplication* modApp, const PipelineFlowState& input)
{
// Reset the stored results in the ModifierApplication.
static_object_cast<HistogramModifierApplication>(modApp)->setHistogramData({},0,0);
static_object_cast<HistogramModifierApplication>(modApp)->setBinCounts({});
if(!propertyClass())
throwException(tr("No input property class selected."));
......@@ -274,9 +280,24 @@ PipelineFlowState HistogramModifier::evaluatePreliminary(TimePoint time, Modifie
intervalStart = intervalEnd = 0;
}
// Output a plot object.
auto xcoords = std::make_shared<PropertyStorage>(histogramData.size(), PropertyStorage::Float, 1, 0, sourceProperty().nameWithComponent(), false);
auto ycoords = std::make_shared<PropertyStorage>(histogramData.size(), PropertyStorage::Int64, 1, 0, tr("Count"), false);
FloatType binSize = (intervalEnd - intervalStart) / histogramData.size();
for(size_t i = 0; i < histogramData.size(); i++) {
xcoords->setFloat(i, binSize * (i + FloatType(0.5)) + intervalStart);
ycoords->setInt64(i, histogramData[i]);
}
OORef<PlotObject> plotObj = new PlotObject(modApp->dataset());
plotObj->setTitle(tr("Histogram [%1]").arg(sourceProperty().nameWithComponent()));
plotObj->setx(xcoords);
plotObj->sety(ycoords);
output.addObject(plotObj);
// Store results in the ModifierApplication.
static_object_cast<HistogramModifierApplication>(modApp)->setHistogramData(std::move(histogramData), intervalStart, intervalEnd);
static_object_cast<HistogramModifierApplication>(modApp)->setBinCounts(ycoords);
static_object_cast<HistogramModifierApplication>(modApp)->setHistogramInterval({intervalStart, intervalEnd});
QString statusMessage;
if(outputSelection) {
statusMessage = tr("%1 %2 selected (%3%)")
......
......@@ -36,7 +36,9 @@ class OVITO_STDMOD_EXPORT HistogramModifier : public GenericPropertyModifier
{
Q_OBJECT
OVITO_CLASS(HistogramModifier)
Q_CLASSINFO("DisplayName", "Histogram");
Q_CLASSINFO("ModifierCategory", "Analysis");
public:
/// Constructor.
......@@ -102,9 +104,6 @@ private:
/// Controls whether the modifier should take into account only selected elements.
DECLARE_MODIFIABLE_PROPERTY_FIELD(bool, onlySelected, setOnlySelected);
Q_CLASSINFO("DisplayName", "Histogram");
Q_CLASSINFO("ModifierCategory", "Analysis");
};
/**
......@@ -120,31 +119,16 @@ public:
/// Constructor.
Q_INVOKABLE HistogramModifierApplication(DataSet* dataset) : ModifierApplication(dataset) {}
/// Returns the stored histogram data.
const QVector<size_t>& histogramData() const { return _histogramData; }
/// Returns the start of the histogram's range along the x-axis.
FloatType intervalStart() const { return _intervalStart; }
/// Returns the end of the histogram's range along the x-axis.
FloatType intervalEnd() const { return _intervalEnd; }
/// Replaces the stored data.
void setHistogramData(QVector<size_t> histogramData, FloatType intervalStart, FloatType intervalEnd) {
_histogramData = std::move(histogramData);
_intervalStart = intervalStart;
_intervalEnd = intervalEnd;
notifyDependents(ReferenceEvent::ObjectStatusChanged);
}
using HistogramInterval = std::pair<FloatType,FloatType>;
private:
/// Stores the histogram data.
QVector<size_t> _histogramData;
/// The bin values of the histogram.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(PropertyPtr, binCounts, setBinCounts, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
FloatType _intervalStart;
FloatType _intervalEnd;
/// The interval of the histogram.
DECLARE_RUNTIME_PROPERTY_FIELD_FLAGS(HistogramInterval, histogramInterval, setHistogramInterval, PROPERTY_FIELD_NO_CHANGE_MESSAGE);
};
} // End of namespace
......
......@@ -456,21 +456,21 @@ PYBIND11_MODULE(StdMod, m)
":Default: ``False``\n")
.def_property_readonly("_histogram_data", py::cpp_function([](HistogramModifier& mod) {
HistogramModifierApplication* modApp = dynamic_object_cast<HistogramModifierApplication>(mod.someModifierApplication());
if(!modApp || modApp->histogramData().empty()) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
py::array_t<size_t> array(modApp->histogramData().size(), modApp->histogramData().data(), py::cast(modApp));
if(!modApp || !modApp->binCounts()) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
py::array_t<qlonglong> array(modApp->binCounts()->size(), modApp->binCounts()->constDataInt64(), py::cast(modApp));
// Mark array as read-only.
reinterpret_cast<py::detail::PyArray_Proxy*>(array.ptr())->flags &= ~py::detail::npy_api::NPY_ARRAY_WRITEABLE_;
return array;
}))
.def_property_readonly("_interval_start", py::cpp_function([](HistogramModifier& mod) {
HistogramModifierApplication* modApp = dynamic_object_cast<HistogramModifierApplication>(mod.someModifierApplication());
if(!modApp) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
return modApp->intervalStart();
if(!modApp || !modApp->binCounts()) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
return modApp->histogramInterval().first;
}))
.def_property_readonly("_interval_end", py::cpp_function([](HistogramModifier& mod) {
HistogramModifierApplication* modApp = dynamic_object_cast<HistogramModifierApplication>(mod.someModifierApplication());
if(!modApp) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
return modApp->intervalEnd();
if(!modApp || !modApp->binCounts()) mod.throwException(HistogramModifier::tr("Modifier has not been evaluated yet. Histogram data is not yet available."));
return modApp->histogramInterval().second;
}))
;
ovito_class<HistogramModifierApplication, ModifierApplication>{m};
......
......@@ -32,6 +32,7 @@ SET(SourceFiles
properties/ElementType.cpp
properties/GenericPropertyModifier.cpp
properties/PropertyExpressionEvaluator.cpp
plot/PlotObject.cpp
util/InputHelper.cpp
util/OutputHelper.cpp
)
......
......@@ -24,6 +24,7 @@ SET(SourceFiles
properties/PropertyInspectionApplet.cpp
simcell/SimulationCellObjectEditor.cpp
simcell/SimulationCellVisEditor.cpp
plot/PlotInspectionApplet.cpp
widgets/PropertyClassParameterUI.cpp
widgets/PropertyReferenceParameterUI.cpp
widgets/PropertySelectionComboBox.cpp
......@@ -35,6 +36,7 @@ QT5_ADD_RESOURCES(ResourceFiles resources/stdobj_gui.qrc)
OVITO_STANDARD_PLUGIN(StdObjGui
SOURCES StdObjGui.cpp ${SourceFiles} ${ResourceFiles}
PLUGIN_DEPENDENCIES StdObj
PRIVATE_LIB_DEPENDENCIES Qwt
GUI_PLUGIN
)
......
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
// OVITO is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// OVITO is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#include <plugins/stdobj/gui/StdObjGui.h>
#include <gui/mainwin/MainWindow.h>
#include "PlotInspectionApplet.h"
#include <qwt/qwt_plot.h>
#include <qwt/qwt_plot_curve.h>
#include <qwt/qwt_plot_grid.h>
namespace Ovito { namespace StdObj {
IMPLEMENT_OVITO_CLASS(PlotInspectionApplet);
/******************************************************************************
* Determines whether the given pipeline flow state contains data that can be
* displayed by this applet.
******************************************************************************/
bool PlotInspectionApplet::appliesTo(const PipelineFlowState& state)
{
return state.findObject<PlotObject>() != nullptr;
}
/******************************************************************************
* Lets the applet create the UI widget that is to be placed into the data
* inspector panel.
******************************************************************************/
QWidget* PlotInspectionApplet::createWidget(MainWindow* mainWindow)
{
QSplitter* splitter = new QSplitter();
_plotSelectionWidget = new QListWidget();
splitter->addWidget(_plotSelectionWidget);
_plotWidget = new QwtPlot();
_plotWidget->setCanvasBackground(Qt::white);
QwtPlotGrid* plotGrid = new QwtPlotGrid();
plotGrid->setPen(Qt::gray, 0, Qt::DotLine);
plotGrid->attach(_plotWidget);
splitter->addWidget(_plotWidget);
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 4);
connect(_plotSelectionWidget, &QListWidget::currentItemChanged, this, &PlotInspectionApplet::currentPlotChanged);
return splitter;
}
/******************************************************************************
* Updates the contents displayed in the inspector.
******************************************************************************/
void PlotInspectionApplet::updateDisplay(const PipelineFlowState& state, PipelineSceneNode* sceneNode)
{
// Remember which plot was previously selected.
QString selectedPlotTitle;
if(plotSelectionWidget()->currentItem())
selectedPlotTitle = plotSelectionWidget()->currentItem()->text();
// Rebuild list of plots.
plotSelectionWidget()->clear();
for(DataObject* obj : state.objects()) {
if(PlotObject* plotObj = dynamic_object_cast<PlotObject>(obj)) {
QListWidgetItem* item = new QListWidgetItem(plotObj->title(), plotSelectionWidget());
item->setData(Qt::UserRole, QVariant::fromValue<OORef<OvitoObject>>(plotObj));
// Select again the previously selected plot.
if(item->text() == selectedPlotTitle)
plotSelectionWidget()->setCurrentItem(item);
}
}
if(!plotSelectionWidget()->currentItem() && plotSelectionWidget()->count() != 0)
plotSelectionWidget()->setCurrentRow(0);
}
/******************************************************************************
* Is called when the user selects a different plot item in the list.
******************************************************************************/
void PlotInspectionApplet::currentPlotChanged(QListWidgetItem* current, QListWidgetItem* previous)
{
OORef<PlotObject> plotObj;
if(current)
plotObj = static_object_cast<PlotObject>(current->data(Qt::UserRole).value<OORef<OvitoObject>>());
_plotWidget->setAxisTitle(QwtPlot::xBottom, QString());
_plotWidget->setAxisTitle(QwtPlot::yLeft, QString());
if(plotObj && plotObj->y()) {
if(!_plotCurve) {
_plotCurve = new QwtPlotCurve();
_plotCurve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
_plotCurve->setBrush(QColor(255, 160, 100));
_plotCurve->attach(_plotWidget);
}
QVector<double> xcoords(plotObj->y()->size());
QVector<double> ycoords(plotObj->y()->size());
if(!plotObj->x() || plotObj->x()->size() != xcoords.size() || !plotObj->x()->copyTo(xcoords.begin())) {
std::iota(xcoords.begin(), xcoords.end(), 0);
}
else _plotWidget->setAxisTitle(QwtPlot::xBottom, plotObj->x()->name());
if(!plotObj->y() || plotObj->y()->size() != ycoords.size() || !plotObj->y()->copyTo(ycoords.begin())) {
std::fill(ycoords.begin(), ycoords.end(), 0.0);
}
else _plotWidget->setAxisTitle(QwtPlot::yLeft, plotObj->y()->name());
_plotCurve->setSamples(xcoords, ycoords);
}
else if(_plotCurve) {
_plotCurve->detach();
delete _plotCurve;
_plotCurve = nullptr;
}
_plotWidget->replot();
}
} // End of namespace
} // End of namespace
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
// OVITO is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// OVITO is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#pragma once
#include <plugins/stdobj/gui/StdObjGui.h>
#include <plugins/stdobj/plot/PlotObject.h>
#include <gui/mainwin/data_inspector/DataInspectionApplet.h>
class QwtPlot;
class QwtPlotCurve;
namespace Ovito { namespace StdObj {
/**
* \brief Data inspector page for 2d plots.
*/
class OVITO_STDOBJGUI_EXPORT PlotInspectionApplet : public DataInspectionApplet
{
Q_OBJECT
OVITO_CLASS(PlotInspectionApplet)
Q_CLASSINFO("DisplayName", "Data Plots");
public:
/// Constructor.
Q_INVOKABLE PlotInspectionApplet() {}
/// Returns the key value for this applet that is used for ordering the applet tabs.
virtual int orderingKey() const override { return 200; }
/// Determines whether the given pipeline flow state contains data that can be displayed by this applet.
virtual bool appliesTo(const PipelineFlowState& state) override;
/// Lets the applet create the UI widget that is to be placed into the data inspector panel.
virtual QWidget* createWidget(MainWindow* mainWindow) override;
/// Lets the applet update the contents displayed in the inspector.
virtual void updateDisplay(const PipelineFlowState& state, PipelineSceneNode* sceneNode) override;
/// Returns the widget for selecting the current data plot.
QListWidget* plotSelectionWidget() const { return _plotSelectionWidget; }
/// Returns the plotting widget.
QwtPlot* plotWidget() const { return _plotWidget; }
private Q_SLOTS:
/// Is called when the user selects a different plot item in the list.
void currentPlotChanged(QListWidgetItem* current, QListWidgetItem* previous);
private:
/// The widget for selecting the current data plot.
QListWidget* _plotSelectionWidget = nullptr;
/// The plotting widget.
QwtPlot* _plotWidget;
/// The plot item.
QwtPlotCurve* _plotCurve = nullptr;
};
} // End of namespace
} // End of namespace
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
// OVITO is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// OVITO is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#include <plugins/stdobj/StdObj.h>
#include <core/dataset/DataSet.h>
#include "PlotObject.h"
namespace Ovito { namespace StdObj {
IMPLEMENT_OVITO_CLASS(PlotObject);
DEFINE_PROPERTY_FIELD(PlotObject, title);
DEFINE_PROPERTY_FIELD(PlotObject, x);
DEFINE_PROPERTY_FIELD(PlotObject, y);
SET_PROPERTY_FIELD_CHANGE_EVENT(PlotObject, title, ReferenceEvent::TitleChanged);
/******************************************************************************
* Constructor.
******************************************************************************/
PlotObject::PlotObject(DataSet* dataset) : DataObject(dataset)
{
}
/******************************************************************************
* Returns the display title of this property object in the user interface.
******************************************************************************/
QString PlotObject::objectTitle()
{
return title();
}
/******************************************************************************
* Saves the class' contents to the given stream.
******************************************************************************/
void PlotObject::saveToStream(ObjectSaveStream& stream, bool excludeRecomputableData)
{
DataObject::saveToStream(stream, excludeRecomputableData);
if(x()) {
stream.beginChunk(0x0101);
x()->saveToStream(stream, excludeRecomputableData);
stream.endChunk();
}
else {
stream.beginChunk(0x0100);
stream.endChunk();
}
if(y()) {
stream.beginChunk(0x0201);
y()->saveToStream(stream, excludeRecomputableData);
stream.endChunk();
}
else {
stream.beginChunk(0x0200);
stream.endChunk();
}
}
/******************************************************************************
* Loads the class' contents from the given stream.
******************************************************************************/
void PlotObject::loadFromStream(ObjectLoadStream& stream)
{
DataObject::loadFromStream(stream);
if(stream.expectChunkRange(0x0100, 2) == 1) {
PropertyPtr s = std::make_shared<PropertyStorage>();
s->loadFromStream(stream);
setx(s);
}
stream.closeChunk();
if(stream.expectChunkRange(0x0200, 2) == 1) {
PropertyPtr s = std::make_shared<PropertyStorage>();
s->loadFromStream(stream);
sety(s);
}
stream.closeChunk();
}
} // End of namespace
} // End of namespace
///////////////////////////////////////////////////////////////////////////////