core/indigo-core/layout/pathway_layout.h (207 lines of code) (raw):

/**************************************************************************** * Copyright (C) from 2009 to Present EPAM Systems. * * This file is part of Indigo toolkit. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ #ifndef __pathway_layout_h__ #define __pathway_layout_h__molecules #include <algorithm> #include <cmath> #include <fstream> #include <functional> #include <iostream> #include <list> #include <numeric> #include <vector> #include "layout/metalayout.h" #include "layout/molecule_layout.h" #include "molecule/meta_commons.h" #include "reaction/pathway_reaction.h" namespace indigo { // The algorithm is based on the following paper: // https://www.researchgate.net/publication/30508504_Improving_Walker's_Algorithm_to_Run_in_Linear_Time // The original Walker's algorithm is described here: // https://www.researchgate.net/publication/220853707_An_Optimal_Algorithm_for_Computing_the_Trees_of_a_Directed_Graph class PathwayLayout { DECL_ERROR; public: static constexpr float COMPONENTS_MARGIN = 1.0f; static constexpr float TEXT_LINE_HEIGHT = 0.36f; static constexpr float ARROW_TAIL_LENGTH = 0.5f; static constexpr float VERTICAL_SPACING = 2.5f; static constexpr float MULTIPATHWAY_VERTICAL_SPACING = 1.5f; static constexpr float ARROW_LENGTH_FACTOR = 7.0f; static constexpr float MIN_BOND_MEAN = 0.01f; static constexpr float TEXT_ADJUSTMENT = 1.5f; static constexpr int MAX_DEPTHS = 10; static constexpr int MAX_SYMBOLS = 30; static constexpr int MIN_LINES_COUNT = 9; static constexpr int ROUNDING_FACTOR = 1000; static constexpr auto REACTION_CONDITIONS = "Reaction Conditions"; static constexpr auto REACTION_NAME = "Name"; static constexpr auto REACTION_PROPERTY_NA = "Not available"; PathwayLayout(PathwayReaction& reaction, const LayoutOptions& options) : _reaction(reaction), _depths(MAX_DEPTHS, 0), _maxDepth(0), _bond_length(options.DEFAULT_BOND_LENGTH), _default_arrow_size((float)options.DEFAULT_BOND_LENGTH * ARROW_LENGTH_FACTOR), _reaction_margin_size(options.reactionComponentMarginSize / options.ppi), _preserve_molecule_layout(true), _text_line_height((float)options.DEFAULT_BOND_LENGTH * TEXT_LINE_HEIGHT) { } void make(); void setPreserveMoleculeLayout(bool preserve) { _preserve_molecule_layout = preserve; }; bool isPreserveMoleculeLayout() const { return _preserve_molecule_layout; }; void generateTextBlocks(SimpleTextObjectBuilder& tob, const ObjArray<Array<char>>& props, const std::string& style, float& height); private: struct PathwayLayoutItem { PathwayLayoutItem(PathwayReaction& pwr, const PathwayLayout& pwl, int nodeIdx, float bondLength, int reactantIdx = -1) : levelTree(-1), prelim(0.0), mod(0.0), shift(0.0), change(0.0), width(0.0), height(0.0), ancestor(this), thread(nullptr), children(), parent(nullptr), nextSibling(nullptr), prevSibling(nullptr), reaction(pwr), boundingBox() { auto& reactionNode = reaction.getReactionNode(nodeIdx); reactionIndex = reactionNode.reactionIdx; // create as a final reactant child if (reactantIdx != -1) { auto& mol = reaction.getMolecule(reactantIdx); auto mean = mol.getBondsMeanLength(); if (!pwl.isPreserveMoleculeLayout() || mean < MIN_BOND_MEAN) { MoleculeLayout ml(mol, true); ml.bond_length = bondLength; ml.make(); } else { Vec2f center; mol.getAtomsCenterPoint(center); mol.scale(center, bondLength / mean); } Rect2f boundingBox; mol.getBoundingBox(boundingBox, Vec2f(bondLength, bondLength)); molecules.push_back(std::make_pair(reactantIdx, boundingBox)); width = boundingBox.width(); height = boundingBox.height(); } else { auto& simpleReaction = reaction.getReaction(reactionIndex); for (auto pidx : simpleReaction.productIndexes) { auto& mol = reaction.getMolecule(pidx); auto mean = mol.getBondsMeanLength(); if (!pwl.isPreserveMoleculeLayout() || mean < MIN_BOND_MEAN) { MoleculeLayout ml(mol, true); ml.bond_length = bondLength; ml.make(); } else { Vec2f center; mol.getAtomsCenterPoint(center); mol.scale(center, bondLength / mean); } Rect2f boundingBox; mol.getBoundingBox(boundingBox); molecules.push_back(std::make_pair(pidx, boundingBox)); width += boundingBox.width(); // add some spacing for plus height = std::max(boundingBox.height(), height); } } width += COMPONENTS_MARGIN * molecules.size(); } PathwayLayoutItem* getFirstChild() { return children.empty() ? nullptr : children.front(); } PathwayLayoutItem* getLastChild() { return children.empty() ? nullptr : children.back(); } void clear() { levelTree = -1; prelim = mod = shift = change = 0.0; ancestor = thread = nullptr; } void applyLayout() { if (!molecules.empty()) { float totalWidth = std::accumulate(molecules.begin(), molecules.end(), 0.0f, [](float acc, const std::pair<int, Rect2f>& r) { return acc + r.second.width(); }); float margin = COMPONENTS_MARGIN * (molecules.size() - 1); float blockWidth = totalWidth + margin; float startX = boundingBox.left() + (boundingBox.width() - blockWidth) / 2; float currentX = startX; float currentY = boundingBox.center().y; for (auto& mol_desc : molecules) { auto& mol = reaction.getMolecule(mol_desc.first); Vec2f item_offset(currentX + mol_desc.second.width() / 2 - mol_desc.second.center().x, currentY - mol_desc.second.center().y); mol.offsetCoordinates(Vec3f(item_offset.x, item_offset.y, 0)); currentX += mol_desc.second.width() + COMPONENTS_MARGIN; } } } void setXY(float x, float y) { boundingBox = Rect2f(Vec2f(x - width, y - height / 2), Vec2f(x, y + height / 2)); } // required for the layout algorithm float width, height; std::vector<PathwayLayoutItem*> children; std::list<PathwayLayoutItem> reactantsNoPrecursors; PathwayLayoutItem* parent; PathwayLayoutItem* nextSibling; PathwayLayoutItem* prevSibling; // computed fields int levelTree; float prelim, mod, shift, change; PathwayLayoutItem* ancestor; PathwayLayoutItem* thread; // other data std::vector<std::pair<int, Rect2f>> molecules; PathwayReaction& reaction; Rect2f boundingBox; int reactionIndex; }; struct PathwayLayoutRootItem { PathwayLayoutRootItem(int root) : rootIndex(root) { } int rootIndex; Rect2f boundingBox; std::vector<PathwayLayout::PathwayLayoutItem*> layoutItems; }; void traverse(PathwayLayoutItem* root, std::function<void(PathwayLayoutItem*, int)> node_processor); float spacing(PathwayLayoutItem* top, PathwayLayoutItem* bottom, bool siblings) { return VERTICAL_SPACING + (top->height + bottom->height) / 2.0f; } void updateDepths(int depth, PathwayLayoutItem* item); void determineDepths(); void buildLayoutTree(); void copyTextPropertiesToNode(const PathwayReaction::SimpleReaction& reaction, PathwayReaction::ReactionNode& node); void firstWalk(PathwayLayoutItem* node, int num, int depth); PathwayLayoutItem* apportion(PathwayLayoutItem* currentNode, PathwayLayoutItem* ancestorNode); PathwayLayoutItem* nextUpper(PathwayLayoutItem* node); PathwayLayoutItem* nextLower(PathwayLayoutItem* node); void moveSubtree(PathwayLayoutItem* parent, PathwayLayoutItem* wp, float shift); void executeShifts(PathwayLayoutItem* node); PathwayLayoutItem* ancestor(PathwayLayoutItem* node1, PathwayLayoutItem* node2, PathwayLayoutItem* ancestor); void secondWalk(PathwayLayoutItem* node, PathwayLayoutItem* parent, float modifier, int depth); void applyLayout(); void addMetaText(PathwayReaction::ReactionNode& node, const Vec2f text_pos_bl, float text_height_limit); std::vector<std::string> splitText(const std::string& text, float max_width, std::function<float(char ch)> symbol_width); std::vector<float> _depths; std::vector<float> _shifts; int _maxDepth = 0; PathwayReaction& _reaction; std::vector<PathwayLayoutItem> _layoutItems; std::vector<PathwayLayoutRootItem> _layoutRootItems; const float _bond_length; const float _text_line_height; const float _default_arrow_size; const float _reaction_margin_size; bool _preserve_molecule_layout; }; } #endif