Repository: biolab/orange3

Issue:
Pythagorean Forest: report does not work
**What's wrong?**

Clicking the report button produces an error. The problem is that widget declares `graph_name = "scene"`, although it doesn't have a "scene" but uses a left-to-right wrapping list view instead.

**How can we reproduce the problem?** 

Give it some data and click the report button.

**What's your environment?**

Irrelevant.


Discussion:

Resolving patch:
diff --git a/Orange/widgets/visualize/owpythagoreanforest.py b/Orange/widgets/visualize/owpythagoreanforest.py
--- a/Orange/widgets/visualize/owpythagoreanforest.py
+++ b/Orange/widgets/visualize/owpythagoreanforest.py
@@ -3,11 +3,13 @@
 from typing import Any, Callable, Optional
 
 from AnyQt.QtCore import Qt, QRectF, QSize, QPointF, QSizeF, QModelIndex, \
-    QItemSelection, QItemSelectionModel, QT_VERSION
+    QItemSelection, QItemSelectionModel, QT_VERSION, QByteArray, QBuffer, \
+    QIODevice
 from AnyQt.QtGui import QPainter, QPen, QColor, QBrush, QMouseEvent
 from AnyQt.QtWidgets import QSizePolicy, QGraphicsScene, QLabel, QSlider, \
     QListView, QStyledItemDelegate, QStyleOptionViewItem, QStyle
 
+from orangewidget.io import PngFormat
 from Orange.base import RandomForestModel, TreeModel
 from Orange.data import Table
 from Orange.widgets import gui, settings
@@ -23,6 +25,53 @@
 from Orange.widgets.widget import OWWidget
 
 
+REPORT_STYLE = """
+<style>
+* {
+  box-sizing: border-box;
+}
+
+.forest_model_row {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 0 4px;
+}
+
+.forest_model_col {
+  flex: 10%;
+  max-width: 10%;
+  padding: 0 4px;
+}
+
+.forest_model_col img {
+  margin-top: 8px;
+  vertical-align: middle;
+}
+
+@media screen and (max-width: 2200px) {
+  .forest_model_col {
+    flex: 25%;
+    max-width: 25%;
+  }
+}
+
+@media screen and (max-width: 1200px) {
+  .forest_model_col {
+    flex: 50%;
+    max-width: 50%;
+  }
+}
+
+@media screen and (max-width: 600px) {
+  .forest_model_col {
+    flex: 100%;
+    max-width: 100%;
+  }
+}
+</style>
+"""
+
+
 class PythagoreanForestModel(PyListModel):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -377,7 +426,30 @@ def commit(self, selection: QItemSelection) -> None:
 
     def send_report(self):
         """Send report."""
-        self.report_plot()
+        model = self.forest_model
+        max_rows = 30
+
+        def item_html(row):
+            img_data = model.data(model.index(row))
+            byte_array = QByteArray()
+            filename = QBuffer(byte_array)
+            filename.open(QIODevice.WriteOnly)
+            PngFormat.write(filename, img_data)
+            img_encoded = byte_array.toBase64().data().decode("utf-8")
+            return f'<img style="width:100%" ' \
+                   f'src="data:image/png;base64,{img_encoded}"/>'
+
+        html = ["<div class='forest_model_row'>"]
+        for i in range(model.rowCount())[:max_rows]:
+            html.append("<div class='forest_model_col'>")
+            html.extend(item_html(i))
+            html.append("</div>")
+        html.append("</div>")
+
+        html = REPORT_STYLE + "".join(html)
+        if model.rowCount() > max_rows:
+            html += "<p>. . .</p>"
+        self.report_raw(html)
 
 
 class SklRandomForestAdapter:


Test patch:
diff --git a/Orange/widgets/visualize/tests/test_owpythagoreanforest.py b/Orange/widgets/visualize/tests/test_owpythagoreanforest.py
--- a/Orange/widgets/visualize/tests/test_owpythagoreanforest.py
+++ b/Orange/widgets/visualize/tests/test_owpythagoreanforest.py
@@ -1,5 +1,5 @@
 # pylint: disable=missing-docstring,protected-access
-
+import unittest
 from unittest.mock import Mock
 
 from AnyQt.QtCore import Qt, QItemSelection, QItemSelectionModel
@@ -238,3 +238,15 @@ def test_context(self):
 
         self.send_signal(self.widget.Inputs.random_forest, iris_tree)
         self.assertEqual(2, self.widget.target_class_index)
+
+    def test_report(self):
+        self.widget.send_report()
+
+        self.widget.report_raw = Mock()
+        self.send_signal(self.widget.Inputs.random_forest, self.titanic)
+        self.widget.send_report()
+        self.widget.report_raw.assert_called_once()
+
+
+if __name__ == "__main__":
+    unittest.main()