Light Markers and Points Selection Example#

The Light Markers and Points Selection example shows how to use light markers and point selections in a series.

QChart with Light Markers shown

Creating the chart and its elements#

We start by creating a series, filling it with the data, and enabling the light marker and point selection features. It is important not to set points visibility to True, because light markers functionality is an independent feature and setting both would result in undesired behavior.

20    marker_size = 20.
21    series = QSplineSeries()
22    series.append([QPointF(0, 0),
23                    QPointF(0.5, 2.27),
24                    QPointF(1.5, 2.2),
25                    QPointF(3.3, 1.7),
26                    QPointF(4.23, 3.1),
27                    QPointF(5.3, 2.3),
28                    QPointF(6.47, 4.1)])
29    series.setMarkerSize(marker_size)
30    series.setLightMarker(Utilities.default_light_marker(marker_size))
31    series.setSelectedLightMarker(Utilities.default_selected_light_marker(marker_size))
32
33    @Slot(QPointF)
34    def toggle_selection(point):
35        try:
36            index = series.points().index(point)
37            if index != -1:
38                series.toggleSelection([index])
39        except ValueError:
40            pass
41
42    series.clicked.connect(toggle_selection)

Then we create the QChart, the QChartview and the control widget with its layout to arrange customization elements.

44    chart = QChart()
45    chart.addSeries(series)
46    chart.createDefaultAxes()
47    chart.legend().setVisible(False)
48
49    chart_view = QChartView(chart)
50    chart_view.setRenderHint(QPainter.Antialiasing)
51
52    control_widget = QWidget(window)
53    control_layout = QGridLayout(control_widget)

Creating UI for configuring the chart#

The next step is where we create user interface elements that allow customizing the chart, including setting light marker and selection marker images.

54    char_point_combobox = QComboBox()
55    char_point_selected_combobox = QComboBox()
56    line_color_combobox = QComboBox()
57    show_unselected_points_checkbox = QCheckBox()

We create the label for the marker selection combobox and fill the combobox with the items. We then provide functionality to the combobox, allowing the user’s selection to set the desired light marker image. As light markers are enabled and disabled by setting a valid QImage or setting an empty QImage(), we need to make sure that if the user does not wish unselected points to be displayed, we do not actually set the light marker image. If checking isn’t performed, a new QImage will be set as the light marker and unselected points will be visible even though it has been switched off.

59    @Slot(int)
60    def set_light_marker(index):
61        if show_unselected_points_checkbox.isChecked():
62            series.setLightMarker(Utilities.get_point_representation(
63                Utilities.point_type(index), marker_size))
64
65    char_point = QLabel("Char point: ")
66    char_point_combobox.addItems(["Red rectangle", "Green triangle", "Orange circle"])
67    char_point_combobox.currentIndexChanged.connect(set_light_marker)

Almost the same procedure applies to the selected point light marker and line color. The only difference is that there is no need to check the visibility of unselected points as it doesn’t affect the functionality.

70    @Slot(int)
71    def set_selected_light_marker(index):
72        series.setSelectedLightMarker(Utilities.get_selected_point_representation(Utilities.selected_point_type(index), marker_size))
73
74    char_point_selected = QLabel("Char point selected: ")
75    char_point_selected_combobox.addItems(["Blue triangle", "Yellow rectangle", "Lavender circle"])
76    char_point_selected_combobox.currentIndexChanged.connect(set_selected_light_marker)
77
78
79    @Slot(int)
80    def set_line_color(index):
81        series.setColor(Utilities.make_line_color(Utilities.line_color(index)))
82
83    line_color_label = QLabel("Line color: ")
84    line_color_combobox.addItems(["Blue", "Black", "Mint"])
85    line_color_combobox.currentIndexChanged.connect(set_line_color)

A small difference comes with changing visibility of unselected points. As it was mentioned before, making light markers invisible is achieved by setting the light marker to an empty QImage(). That is why, depending on checkbox state, selected point light marker is set to an empty QImage or to the light marker extracted from the current index of the corresponding combobox.

88    @Slot(int)
89    def display_unselected_points(checkbox_state):
90        if checkbox_state:
91            series.setLightMarker(Utilities.get_point_representation(Utilities.point_type(char_point_combobox.currentIndex()), marker_size))
92        else:
93            series.setLightMarker(QImage())
94
95    show_unselected_points_label = QLabel("Display unselected points: ")
96    show_unselected_points_checkbox.setChecked(True)
97    show_unselected_points_checkbox.stateChanged.connect(display_unselected_points)

The final part is to lay out the widgets within the main widget and set the main window size.

Usage#

To use this example, change any of the comboboxes and checkboxes controlling the markers, line color, and unselected point visibility on the right. Then try clicking on points in the chart to select or deselect them.

Download this example

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

"""PySide6 port of the Light Markers Points Selection example from Qt v6.2"""
import sys

from PySide6.QtCore import Slot, QPointF, Qt
from PySide6.QtCharts import QChart, QChartView, QSplineSeries
from PySide6.QtGui import QPainter, QImage
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QComboBox, QCheckBox, QLabel, QHBoxLayout

import utilities as Utilities

if __name__ == "__main__":

    a = QApplication(sys.argv)
    window = QMainWindow()
    window.setWindowTitle("Light Markers and Points Selection")

    marker_size = 20.
    series = QSplineSeries()
    series.append([QPointF(0, 0),
                    QPointF(0.5, 2.27),
                    QPointF(1.5, 2.2),
                    QPointF(3.3, 1.7),
                    QPointF(4.23, 3.1),
                    QPointF(5.3, 2.3),
                    QPointF(6.47, 4.1)])
    series.setMarkerSize(marker_size)
    series.setLightMarker(Utilities.default_light_marker(marker_size))
    series.setSelectedLightMarker(Utilities.default_selected_light_marker(marker_size))

    @Slot(QPointF)
    def toggle_selection(point):
        try:
            index = series.points().index(point)
            if index != -1:
                series.toggleSelection([index])
        except ValueError:
            pass

    series.clicked.connect(toggle_selection)

    chart = QChart()
    chart.addSeries(series)
    chart.createDefaultAxes()
    chart.legend().setVisible(False)

    chart_view = QChartView(chart)
    chart_view.setRenderHint(QPainter.Antialiasing)

    control_widget = QWidget(window)
    control_layout = QGridLayout(control_widget)
    char_point_combobox = QComboBox()
    char_point_selected_combobox = QComboBox()
    line_color_combobox = QComboBox()
    show_unselected_points_checkbox = QCheckBox()

    @Slot(int)
    def set_light_marker(index):
        if show_unselected_points_checkbox.isChecked():
            series.setLightMarker(Utilities.get_point_representation(
                Utilities.point_type(index), marker_size))

    char_point = QLabel("Char point: ")
    char_point_combobox.addItems(["Red rectangle", "Green triangle", "Orange circle"])
    char_point_combobox.currentIndexChanged.connect(set_light_marker)


    @Slot(int)
    def set_selected_light_marker(index):
        series.setSelectedLightMarker(Utilities.get_selected_point_representation(Utilities.selected_point_type(index), marker_size))

    char_point_selected = QLabel("Char point selected: ")
    char_point_selected_combobox.addItems(["Blue triangle", "Yellow rectangle", "Lavender circle"])
    char_point_selected_combobox.currentIndexChanged.connect(set_selected_light_marker)


    @Slot(int)
    def set_line_color(index):
        series.setColor(Utilities.make_line_color(Utilities.line_color(index)))

    line_color_label = QLabel("Line color: ")
    line_color_combobox.addItems(["Blue", "Black", "Mint"])
    line_color_combobox.currentIndexChanged.connect(set_line_color)


    @Slot(int)
    def display_unselected_points(checkbox_state):
        if checkbox_state:
            series.setLightMarker(Utilities.get_point_representation(Utilities.point_type(char_point_combobox.currentIndex()), marker_size))
        else:
            series.setLightMarker(QImage())

    show_unselected_points_label = QLabel("Display unselected points: ")
    show_unselected_points_checkbox.setChecked(True)
    show_unselected_points_checkbox.stateChanged.connect(display_unselected_points)


    control_label = QLabel("Marker and Selection Controls")
    control_label.setAlignment(Qt.AlignHCenter)
    control_label_font = control_label.font()
    control_label_font.setBold(True)
    control_label.setFont(control_label_font)
    control_layout.addWidget(control_label, 0, 0, 1, 2)
    control_layout.addWidget(char_point, 1, 0)
    control_layout.addWidget(char_point_combobox, 1, 1)

    control_layout.addWidget(char_point_selected, 2, 0)
    control_layout.addWidget(char_point_selected_combobox, 2, 1)

    control_layout.addWidget(line_color_label, 3, 0)
    control_layout.addWidget(line_color_combobox, 3, 1)

    control_layout.addWidget(show_unselected_points_label, 4, 0)
    control_layout.addWidget(show_unselected_points_checkbox, 4, 1, 1, 2)
    control_layout.setRowStretch(5, 1)

    main_widget = QWidget(window)
    main_layout = QHBoxLayout(main_widget)
    main_layout.addWidget(chart_view)
    main_layout.addWidget(control_widget)

    window.setCentralWidget(main_widget)
    window.resize(1080, 720)
    window.show()
    sys.exit(a.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtGui import QImage, QPainter, QColor
from PySide6.QtCore import Qt

import rc_markers

def rectangle(point_type, image_size):
    image = QImage(image_size, image_size, QImage.Format_RGB32)
    painter = QPainter()
    painter.begin(image)
    painter.setRenderHint(QPainter.Antialiasing)
    painter.fillRect(0, 0, image_size, image_size, point_type[2])
    painter.end()
    return image

def triangle(point_type, image_size):
    return QImage(point_type[3]).scaled(image_size, image_size)

def circle(point_type, image_size):
    image = QImage(image_size, image_size, QImage.Format_ARGB32)
    image.fill(QColor(0, 0, 0, 0))
    painter = QPainter()
    painter.begin(image)
    painter.setRenderHint(QPainter.Antialiasing)
    painter.setBrush(point_type[2])
    pen = painter.pen()
    pen.setWidth(0)
    painter.setPen(pen)
    painter.drawEllipse(0, 0, image_size * 0.9, image_size * 0.9)
    painter.end()
    return image

_point_types = [("RedRectangle", rectangle, Qt.red),
                ("GreenTriangle", triangle, Qt.green, ":/images/green_triangle.png"),
                ("OrangeCircle", circle, QColor(255, 127, 80))]
_selected_point_types = [("BlueTriangle", triangle, Qt.blue, ":/images/blue_triangle.png"),
                         ("YellowRectangle", rectangle, Qt.yellow),
                         ("LavenderCircle", circle, QColor(147, 112, 219))]
_line_colors = [("Blue", QColor(65, 105, 225)), ("Black", Qt.black), ("Mint", QColor(70, 203, 155))]

def point_type(index):
    return _point_types[index]

def selected_point_type(index):
    return _selected_point_types[index]

def line_color(index):
    return _line_colors[index]


def default_light_marker(image_size):
    return rectangle(_point_types[0], image_size)

def default_selected_light_marker(image_size):
    return triangle(_selected_point_types[0], image_size)


def get_point_representation(point_type, image_size):
    return point_type[1](point_type, image_size)

def get_selected_point_representation(point_type, image_size):
    return point_type[1](point_type, image_size)

def make_line_color(line_color):
    return line_color[1]
<RCC>
    <qresource prefix="/">
        <file>images/blue_triangle.png</file>
        <file>images/green_triangle.png</file>
    </qresource>
</RCC>