def __init__(self, parent=None):
        super(ExplicitBezierCurveWidget, self).__init__(parent)

        graphics_view = QGraphicsView()

        def graphics_view_resize_event(event):
            assert isinstance(event, QResizeEvent)
            graphics_view.fitInView(QRectF(0, -SCENE_SIZE_2, SCENE_SIZE, SCENE_SIZE), Qt.KeepAspectRatio)
            super(QGraphicsView, graphics_view).resizeEvent(event)

        graphics_view.resizeEvent = graphics_view_resize_event
        self.explicit_bezier_curve_scene = ExplicitBezierCurveScene(self)
        graphics_view.setScene(self.explicit_bezier_curve_scene)
        graphics_view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)

        properties_group_box = QGroupBox()
        properties_group_box.setTitle("Properties")

        properties_group_box_layout = QVBoxLayout()
        properties_group_box.setLayout(properties_group_box_layout)

        # Degree

        degree_layout = QHBoxLayout()
        properties_group_box_layout.addLayout(degree_layout)
        degree_label = QLabel("Polynomial degree")
        self.degree_spinbox = QSpinBox()
        self.degree_spinbox.setRange(MIN_POLYNOMIAL_DEGREE, MAX_POLYNOMIAL_DEGREE)
        self.degree_spinbox.setValue(POLYNOMIAL_DEGREE_DEFAULT)
        self.degree_spinbox.valueChanged.connect(self.process_degree_changed)
        degree_label.setBuddy(self.degree_spinbox)
        degree_layout.addWidget(degree_label)
        degree_layout.addWidget(self.degree_spinbox)

        # X - range

        x_layout = QHBoxLayout()
        x_label = QLabel("X:")
        x_layout.addWidget(x_label)
        properties_group_box_layout.addLayout(x_layout)

        min_x_value_label = QLabel("Min")
        x_layout.addWidget(min_x_value_label)
        self.min_x_value_spinbox = QDoubleSpinBox()
        x_layout.addWidget(self.min_x_value_spinbox)
        self.min_x_value_spinbox.setRange(MIN_X_VALUE, DEFAULT_MAX_X_VALUE-MIN_X_RANGE)
        self.min_x_value_spinbox.setValue(DEFAULT_MIN_X_VALUE)
        self.min_x_value_spinbox.valueChanged.connect(self.process_min_x_value_changed)

        max_x_value_label = QLabel("Max")
        x_layout.addWidget(max_x_value_label)
        self.max_x_value_spinbox = QDoubleSpinBox()
        x_layout.addWidget(self.max_x_value_spinbox)
        self.max_x_value_spinbox.setRange(DEFAULT_MIN_X_VALUE - MIN_X_RANGE, MAX_X_VALUE)
        self.max_x_value_spinbox.setValue(DEFAULT_MAX_X_VALUE)
        self.max_x_value_spinbox.valueChanged.connect(self.process_max_x_value_changed)

        # Y - range

        y_layout = QHBoxLayout()
        properties_group_box_layout.addLayout(y_layout)
        y_label = QLabel("Y:")
        y_layout.addWidget(y_label)

        min_y_value_label = QLabel("Min")
        y_layout.addWidget(min_y_value_label)
        self.min_y_value_spinbox = QDoubleSpinBox()
        self.min_y_value_spinbox.setRange(MIN_Y_VALUE, DEFAULT_MAX_Y_VALUE-MIN_Y_RANGE)
        self.min_y_value_spinbox.setValue(DEFAULT_MIN_Y_VALUE)
        self.min_y_value_spinbox.valueChanged.connect(self.process_min_value_changed)
        min_y_value_label.setBuddy(self.min_y_value_spinbox)
        y_layout.addWidget(self.min_y_value_spinbox)

        max_y_value_label = QLabel("Max")
        y_layout.addWidget(max_y_value_label)
        self.max_y_value_spinbox = QDoubleSpinBox()
        self.max_y_value_spinbox.setRange(DEFAULT_MIN_Y_VALUE - MIN_Y_RANGE, MAX_Y_VALUE)
        self.max_y_value_spinbox.setValue(DEFAULT_MAX_Y_VALUE)
        self.max_y_value_spinbox.valueChanged.connect(self.process_max_value_changed)
        max_y_value_label.setBuddy(self.max_y_value_spinbox)
        y_layout.addWidget(self.max_y_value_spinbox)

        properties_group_box_layout.addStretch()

        self.polynom_widget = LatexLabelWidget()

        main_layout = QVBoxLayout()

        upper_layout = QVBoxLayout()
        upper_layout.addWidget(graphics_view)
        upper_layout.addWidget(properties_group_box)

        main_layout.addLayout(upper_layout)
        main_layout.addWidget(self.polynom_widget)

        self.setLayout(main_layout)
        self.process_control_points_changed()
class ExplicitBezierCurveWidget(QWidget):

    bezier_curve_changed = Signal(dict, name="bezier_curve_changed")

    def __init__(self, parent=None):
        super(ExplicitBezierCurveWidget, self).__init__(parent)

        graphics_view = QGraphicsView()

        def graphics_view_resize_event(event):
            assert isinstance(event, QResizeEvent)
            graphics_view.fitInView(QRectF(0, -SCENE_SIZE_2, SCENE_SIZE, SCENE_SIZE), Qt.KeepAspectRatio)
            super(QGraphicsView, graphics_view).resizeEvent(event)

        graphics_view.resizeEvent = graphics_view_resize_event
        self.explicit_bezier_curve_scene = ExplicitBezierCurveScene(self)
        graphics_view.setScene(self.explicit_bezier_curve_scene)
        graphics_view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)

        properties_group_box = QGroupBox()
        properties_group_box.setTitle("Properties")

        properties_group_box_layout = QVBoxLayout()
        properties_group_box.setLayout(properties_group_box_layout)

        # Degree

        degree_layout = QHBoxLayout()
        properties_group_box_layout.addLayout(degree_layout)
        degree_label = QLabel("Polynomial degree")
        self.degree_spinbox = QSpinBox()
        self.degree_spinbox.setRange(MIN_POLYNOMIAL_DEGREE, MAX_POLYNOMIAL_DEGREE)
        self.degree_spinbox.setValue(POLYNOMIAL_DEGREE_DEFAULT)
        self.degree_spinbox.valueChanged.connect(self.process_degree_changed)
        degree_label.setBuddy(self.degree_spinbox)
        degree_layout.addWidget(degree_label)
        degree_layout.addWidget(self.degree_spinbox)

        # X - range

        x_layout = QHBoxLayout()
        x_label = QLabel("X:")
        x_layout.addWidget(x_label)
        properties_group_box_layout.addLayout(x_layout)

        min_x_value_label = QLabel("Min")
        x_layout.addWidget(min_x_value_label)
        self.min_x_value_spinbox = QDoubleSpinBox()
        x_layout.addWidget(self.min_x_value_spinbox)
        self.min_x_value_spinbox.setRange(MIN_X_VALUE, DEFAULT_MAX_X_VALUE-MIN_X_RANGE)
        self.min_x_value_spinbox.setValue(DEFAULT_MIN_X_VALUE)
        self.min_x_value_spinbox.valueChanged.connect(self.process_min_x_value_changed)

        max_x_value_label = QLabel("Max")
        x_layout.addWidget(max_x_value_label)
        self.max_x_value_spinbox = QDoubleSpinBox()
        x_layout.addWidget(self.max_x_value_spinbox)
        self.max_x_value_spinbox.setRange(DEFAULT_MIN_X_VALUE - MIN_X_RANGE, MAX_X_VALUE)
        self.max_x_value_spinbox.setValue(DEFAULT_MAX_X_VALUE)
        self.max_x_value_spinbox.valueChanged.connect(self.process_max_x_value_changed)

        # Y - range

        y_layout = QHBoxLayout()
        properties_group_box_layout.addLayout(y_layout)
        y_label = QLabel("Y:")
        y_layout.addWidget(y_label)

        min_y_value_label = QLabel("Min")
        y_layout.addWidget(min_y_value_label)
        self.min_y_value_spinbox = QDoubleSpinBox()
        self.min_y_value_spinbox.setRange(MIN_Y_VALUE, DEFAULT_MAX_Y_VALUE-MIN_Y_RANGE)
        self.min_y_value_spinbox.setValue(DEFAULT_MIN_Y_VALUE)
        self.min_y_value_spinbox.valueChanged.connect(self.process_min_value_changed)
        min_y_value_label.setBuddy(self.min_y_value_spinbox)
        y_layout.addWidget(self.min_y_value_spinbox)

        max_y_value_label = QLabel("Max")
        y_layout.addWidget(max_y_value_label)
        self.max_y_value_spinbox = QDoubleSpinBox()
        self.max_y_value_spinbox.setRange(DEFAULT_MIN_Y_VALUE - MIN_Y_RANGE, MAX_Y_VALUE)
        self.max_y_value_spinbox.setValue(DEFAULT_MAX_Y_VALUE)
        self.max_y_value_spinbox.valueChanged.connect(self.process_max_value_changed)
        max_y_value_label.setBuddy(self.max_y_value_spinbox)
        y_layout.addWidget(self.max_y_value_spinbox)

        properties_group_box_layout.addStretch()

        self.polynom_widget = LatexLabelWidget()

        main_layout = QVBoxLayout()

        upper_layout = QVBoxLayout()
        upper_layout.addWidget(graphics_view)
        upper_layout.addWidget(properties_group_box)

        main_layout.addLayout(upper_layout)
        main_layout.addWidget(self.polynom_widget)

        self.setLayout(main_layout)
        self.process_control_points_changed()

    def process_degree_changed(self, value):
        self.explicit_bezier_curve_scene.set_polynomial_degree(value)
        self.process_control_points_changed()

    def process_max_x_value_changed(self, max_x_value):
        self.min_x_value_spinbox.setRange(MIN_X_VALUE, max_x_value-MIN_X_RANGE)
        self.process_control_points_changed()

    def process_min_x_value_changed(self, min_x_value):
        self.max_y_value_spinbox.setRange(min_x_value - MIN_X_RANGE, MAX_X_VALUE)
        self.process_control_points_changed()

    def process_max_value_changed(self, max_value):
        self.min_y_value_spinbox.setRange(MIN_Y_VALUE, max_value-MIN_Y_RANGE)
        self.process_control_points_changed()

    def process_min_value_changed(self, min_value):
        self.max_y_value_spinbox.setRange(min_value - MIN_Y_RANGE, MAX_Y_VALUE)
        self.process_control_points_changed()

    def process_control_points_changed(self):

        control_points = self.explicit_bezier_curve_scene.control_points

        n = len(control_points) - 1

        x_0 = self.map_x_from_scene(control_points[0].pos().x())
        x_1 = self.map_x_from_scene(control_points[-1].pos().x())

        x, result = symbols('x result')

        result = 0

        for i in range(0, n + 1):
            y_i = self.map_y_from_scene(control_points[i].pos().y())
            result += binom(n, i) * ((x_1 - x) / (x_1 - x_0)) ** (n - i) * ((x - x_0) / (x_1 - x_0)) ** i * y_i

        self.polynom_widget.set_latex_expression(
            latex(N(simplify(expand(result)), 3)))

        self.bezier_curve_changed.emit(self.serialize())

    def map_y_from_scene(self, y):
        max_y = self.max_y_value_spinbox.value()
        min_y = self.min_y_value_spinbox.value()
        a = (max_y-min_y)/(MAX_LINE_Y-MIN_LINE_Y)
        b = -(max_y*MIN_LINE_Y-min_y*MAX_LINE_Y)/(MAX_LINE_Y-MIN_LINE_Y)
        return a*y+b

    def map_x_from_scene(self, x):
        max_x = self.max_x_value_spinbox.value()
        min_x = self.min_x_value_spinbox.value()
        a = (max_x-min_x)/(MAX_LINE_X-MIN_LINE_X)
        b = -(max_x*MIN_LINE_X-min_x*MAX_LINE_X)/(MAX_LINE_X-MIN_LINE_X)
        return a*x+b

    def map_y_to_scene(self, y):
        max_y = self.max_y_value_spinbox.value()
        min_y = self.min_y_value_spinbox.value()
        a = (MAX_LINE_Y-MIN_LINE_Y)/(max_y-min_y)
        b = (max_y*MIN_LINE_Y-min_y*MAX_LINE_Y)/(max_y-min_y)
        return a*y+b

    def serialize(self):
        """
        :return: widget state in the following format
        {
          "min_x" : -10,
          "max_x" :  10,
          "min_y" : -10,
          "max_y" :  10,
          "degree": 3,
          "ys"    : [3, 3, 3, 2]
        }
        """

        control_points = self.explicit_bezier_curve_scene.control_points

        state = dict()
        state["min_x"] = self.min_x_value_spinbox.value()
        state["max_x"] = self.max_x_value_spinbox.value()
        state["min_y"] = self.min_y_value_spinbox.value()
        state["max_y"] = self.max_y_value_spinbox.value()
        state["degree"] = self.degree_spinbox.value()
        state["ys"] = [self.map_y_from_scene(control_point.pos().y()) for control_point in control_points]
        return state

    def deserialize(self, state):
        """
        This method setups widget controls to the state specified.
        :param state: widget state in the following format
        {
          "min_x" : -10,
          "max_x" :  10,
          "min_y" : -10,
          "max_y" :  10,
          "degree": 3,
          "ys"    : [3, 3, 3, 2]
        }
        :return:
        """

        self.min_x_value_spinbox.setValue(state["min_x"])
        self.max_x_value_spinbox.setValue(state["max_x"])
        self.min_y_value_spinbox.setValue(state["min_y"])
        self.max_y_value_spinbox.setValue(state["max_y"])
        self.degree_spinbox.setValue(state["degree"])

        ys = [self.map_y_to_scene(y) for y in state["ys"]]

        n = self.degree_spinbox.value()

        for i, (x, y) in enumerate(zip(linspace(0.02*SCENE_SIZE, 0.98*SCENE_SIZE, num=n + 1), ys)):
            self.explicit_bezier_curve_scene.control_points[i].setPos(QPointF(x, y))