dyng
DynamicGraphLayout
interpolator.h
1 /*
2  Copyright 2020 František Bráblík
3 
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16 #pragma once
17 
18 #include "dynamic_graph.h"
19 
20 #include <cmath> // std::floor, std::ceil
21 #include <algorithm> // std::min, std::count
22 #include <stdexcept> // std::out_of_range
23 #include <utility> // std::move
24 
25 namespace dyng {
26 
27 enum class phase {
29  idle,
31  appear,
33  disappear,
35  morph,
37  simultaneous
38 };
39 
41 struct simultaneous {};
42 
44 struct phased {};
45 
47 
55 class interpolator {
56 
57 private:
58  struct frame_state {
59  float interpolation = 0;
60  float alpha = 0;
61  bool adding = false;
62  bool added = false;
63  bool deleting = false;
64  bool deleted = false;
65  };
66 
67 public:
69 
76  : m_phases{ phase::idle, phase::disappear, phase::morph, phase::appear } {}
77 
79 
86  : m_phases{ phase::idle, phase::simultaneous } {}
87 
89 
95 
97 
102  interpolator(std::vector<phase> phases) {
103  set_phases(std::move(phases));
104  }
105 
107 
122  void set_phases(std::vector<phase> phases) {
123  auto count = [&](phase p){
124  return std::count(phases.begin(), phases.end(), p);
125  };
126  unsigned a = count(phase::appear);
127  unsigned d = count(phase::disappear);
128  unsigned m = count(phase::morph);
129  unsigned s = count(phase::simultaneous);
130  bool either_three = a > 0 || d > 0 || m > 0;
131  bool three_correct = a == 1 && d == 1 && m == 1;
132  if (s > 1 || a > 1 || d > 1 || m > 1) {
133  throw std::invalid_argument("a phase != idle present multiple times");
134  }
135  if ((s == 0 && !three_correct)
136  || (s == 1 && either_three)) {
137  throw std::invalid_argument("invalid phases");
138  }
139  m_phases = std::move(phases);
140  }
141 
142  const std::vector<phase>& get_phases() const { return m_phases; }
143 
145 
153  float& duration(phase p) {
154  return const_cast<float&>(const_cast<const interpolator*>(this)->duration(p));
155  }
156 
158 
166  const float& duration(phase p) const {
167  switch (p) {
168  case phase::idle: return m_idle_time;
169  case phase::appear: return m_appear_time;
170  case phase::disappear: return m_disappear_time;
171  case phase::morph: return m_morph_time;
172  case phase::simultaneous: return m_simultaneous_time;
173  default: throw std::out_of_range("invalid phase");
174  }
175  }
176 
178 
181  float transition_duration() const {
182  float total = 0;
183  for (const auto& p : m_phases) {
184  total += duration(p);
185  }
186  return total;
187  }
188 
190 
195  float length(const dynamic_graph& dgraph) const {
196  return (dgraph.states().size() - 1) * transition_duration();
197  }
198 
200 
207  graph_state operator()(const dynamic_graph& dgraph, float time) const {
208  if (time < 0) {
209  throw std::out_of_range("time < 0");
210  }
211  if (time > length(dgraph)) {
212  throw std::out_of_range("time > length()");
213  }
214  if (dgraph.states().empty()) {
215  return graph_state();
216  }
217  unsigned index_one = std::floor(time / transition_duration());
218  unsigned index_two = std::ceil(time / transition_duration());
219  float value = time - index_one * transition_duration();
220 
221  frame_state animation;
222 
223  std::pair<float, unsigned> current = get_current_phase(value);
224  // perform all previous phases
225  for (unsigned i = 0; i < current.second; ++i) {
226  perform_phase(m_phases[i], duration(m_phases[i]), animation);
227  }
228  // perform current phase according to current time
229  perform_phase(m_phases[current.second], current.first, animation);
230 
231  index_one = std::min<unsigned>(index_one, dgraph.states().size() - 1);
232  index_two = std::min<unsigned>(index_two, dgraph.states().size() - 1);
233  graph_state current_state = dgraph.states()[index_one];
234  const graph_state& next_state = dgraph.states()[index_two];
235 
236  // add new nodes and edges from the next state
237  for (auto& n : current_state.nodes()) {
238  n.is_new(false);
239  }
240  for (auto& e : current_state.edges()) {
241  e.is_new(false);
242  }
243  for (auto n : next_state.nodes()) {
244  if (n.is_new()) {
245  n.is_old(false);
246  current_state.push_node(n);
247  }
248  }
249  for (auto e : next_state.edges()) {
250  if (e.is_new()) {
251  e.is_old(false);
252  current_state.push_edge(e);
253  }
254  }
255 
256  // interpolate between the two states and assign alpha values
257  for (auto& node : current_state.nodes()) {
258  if (next_state.node_exists(node.id())) {
259  const auto& next = next_state.node_at(node.id());
260  node.pos().x = lerp(node.pos().x, next.pos().x, animation.interpolation);
261  node.pos().y = lerp(node.pos().y, next.pos().y, animation.interpolation);
262  }
263  calc_alpha(node, animation);
264  }
265  for (auto& edge : current_state.edges()) {
266  calc_alpha(edge, animation);
267  }
268  return current_state;
269  }
270 
271 private:
272  std::vector<phase> m_phases;
273  float m_idle_time = 0.5;
274  float m_appear_time = 0.25;
275  float m_disappear_time = 0.25;
276  float m_morph_time = 1.0;
277  float m_simultaneous_time = 1.5;
278 
279  float lerp(float a, float b, float value) const {
280  return a + value * (b - a);
281  }
282 
283  // calculates and assigns alpha to an element
284  template<typename T>
285  void calc_alpha(T& element, const frame_state& animation) const {
286  bool is_new = element.is_new();
287  bool is_old = element.is_old();
288  if (!is_old && !is_new) {
289  return;
290  }
291  if (is_new && !animation.added) {
292  element.alpha(0);
293  }
294  if (is_old && animation.deleted) {
295  element.alpha(0);
296  }
297  bool ape = is_new && animation.adding && !animation.added;
298  bool dis = is_old && animation.deleting;
299  if (ape || dis) {
300  element.alpha((!ape + animation.alpha * ape)
301  * (1.0f - animation.alpha * dis));
302  }
303  }
304 
305  std::pair<float, unsigned> get_current_phase(float time) const {
306  for (unsigned i = 0; i < m_phases.size(); ++i) {
307  if (time < duration(m_phases[i])) {
308  return { time, i };
309  }
310  time -= duration(m_phases[i]);
311  }
312  // this should never happen
313  throw std::out_of_range("time overflow phases");
314  }
315 
316  void perform_phase(phase p, float time, frame_state& animation) const {
317  float d = duration(p);
318  switch (p) {
319  case phase::idle:
320  break;
321  case phase::appear:
322  animation.adding = time < d;
323  animation.alpha = time / d;
324  if (time >= d) {
325  animation.added = true;
326  }
327  break;
328  case phase::disappear:
329  animation.deleting = time < d;
330  animation.alpha = time / d;
331  if (time >= d) {
332  animation.deleted = true;
333  }
334  break;
335  case phase::morph:
336  animation.interpolation = time / d;
337  break;
338  case phase::simultaneous:
339  animation.adding = time < d;
340  animation.deleting = time < d;
341  animation.alpha = time / d;
342  animation.interpolation = time / d;
343  if (time >= d) {
344  animation.deleted = true;
345  animation.added = true;
346  }
347  break;
348  }
349  }
350 };
351 
352 } // namespace dyng
dyng::interpolator::duration
float & duration(phase p)
You can read or set the duration of a specific phase type.
Definition: interpolator.h:153
dyng::phased
Tag object that indicates to use default phased interpolation.
Definition: interpolator.h:44
dyng::graph::node_at
const NodeType & node_at(node_id id) const
Returns reference to node of given id.
Definition: graph.h:131
dyng::simultaneous
Tag object that indicates to use default simultaneous interpolation.
Definition: interpolator.h:41
dyng::graph::push_node
NodeType & push_node(NodeType node)
Adds a new node to the graph.
Definition: graph.h:238
dyng::dynamic_graph::states
std::vector< graph_state > & states()
Returns reference to the vector of graph states.
Definition: dynamic_graph.h:157
dyng::graph::nodes
const std::vector< NodeType > & nodes() const
Returns const reference to the vector of all nodes in the graph.
Definition: graph.h:96
dyng::graph::push_edge
EdgeType & push_edge(EdgeType edge)
Adds a new edge to the graph.
Definition: graph.h:256
dyng::interpolator
Used to create a smooth animation from a sequence of states given by a dynamic graph.
Definition: interpolator.h:55
dyng::graph::node_exists
bool node_exists(node_id id) const
Returns whether a node of a given id exists.
Definition: graph.h:296
dyng::interpolator::operator()
graph_state operator()(const dynamic_graph &dgraph, float time) const
Returns a graph state representing a single frame of animation.
Definition: interpolator.h:207
dyng::node
Definition: node.h:32
dyng::edge
Definition: edge.h:119
dyng::interpolator::set_phases
void set_phases(std::vector< phase > phases)
Sets custom order of phases.
Definition: interpolator.h:122
dyng::interpolator::duration
const float & duration(phase p) const
You can read the duration of a specific phase type.
Definition: interpolator.h:166
dyng::node::id
node_id id() const
Returns the id.
Definition: node.h:39
dyng::interpolator::interpolator
interpolator(phased)
Sets up the default order of phases.
Definition: interpolator.h:75
dyng::dynamic_graph
Represents the sequence of states of a dynamic graph.
Definition: dynamic_graph.h:42
dyng::node::pos
const coords & pos() const
Returns current coordinates.
Definition: node.h:66
dyng::interpolator::length
float length(const dynamic_graph &dgraph) const
Returns the total length of the animation.
Definition: interpolator.h:195
dyng::interpolator::interpolator
interpolator(std::vector< phase > phases)
Initializes a custom order of phases.
Definition: interpolator.h:102
dyng::graph::edges
const std::vector< EdgeType > & edges() const
Returns const reference to the vector of all edges in the graph.
Definition: graph.h:112
dyng::interpolator::interpolator
interpolator(simultaneous)
Sets up the default order of phases creating a simultaneous transition.
Definition: interpolator.h:85
dyng::interpolator::transition_duration
float transition_duration() const
Returns the total length of a transition between two states.
Definition: interpolator.h:181
dyng::graph
Templated class for representing a static graph and its layout.
Definition: graph.h:40
dyng::interpolator::interpolator
interpolator()
Sets up the default order of phases.
Definition: interpolator.h:94