Coverage for graphica/gui/dataset_style_icon.py: 96.5%

109 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""データセット一覧の各項目のアイコン(見た目の小さなプレビューと、表示/非表示の目)。 

2 

3main_window と dataset_mixin の両方が使うので、循環 import を避けて別のモジュールにしている。 

4""" 

5from PySide6.QtCore import QPointF, Qt 

6from PySide6.QtGui import QPixmap, QPainter, QPainterPath, QPen, QColor, QBrush, QIcon 

7 

8from graphica.core.dataset import COLOR_BY_COLUMN_PLOT_TYPE, linestyle_name 

9from graphica.core.marker_style import MARKER_EDGE_COLOR, MARKER_FILL_COLOR, MARKER_FILL_HOLLOW, marker_is_unfilled 

10from graphica.gui.icon_utils import icon as _icon_from_svg 

11 

12_STYLE_ICON_SIZE = (28, 14) 

13 

14# 列0はアイコンと名前、列1は表示/非表示の目 

15DATASET_TREE_NAME_COLUMN = 0 

16DATASET_TREE_VISIBILITY_COLUMN = 1 

17 

18# 線種の表示名(linestyle_name で揃えたもの)から Qt のペン。記号('--')だけ見ると 'dashed' が実線になる 

19_LINESTYLE_TO_QT_PEN = { 

20 'solid': Qt.PenStyle.SolidLine, 

21 'dashed': Qt.PenStyle.DashLine, 

22 'dashdot': Qt.PenStyle.DashDotLine, 

23 'dotted': Qt.PenStyle.DotLine, 

24} 

25 

26 

27# マーカーを描く種類 

28MARKER_PLOT_TYPES = ('Scatter', 'Line+Scatter', 'Density Scatter', COLOR_BY_COLUMN_PLOT_TYPE) 

29# マーカーを描かない組み込みの種類(プラグインの種類はマーカーを使うかもしれない) 

30NON_MARKER_PLOT_TYPES = ('Line', 'Area', 'Bar', 'Step') 

31 

32 

33def marker_painter_path(marker, size): 

34 """matplotlib のマーカーの形を、原点を中心にした幅 size px ほどの QPainterPath にする。描かない値は None。""" 

35 from matplotlib.markers import MarkerStyle 

36 from matplotlib.path import Path as MplPath 

37 

38 try: 

39 style = MarkerStyle(marker) 

40 except ValueError: 

41 return None 

42 path = style.get_path().transformed(style.get_transform()) 

43 if len(path.vertices) == 0: 

44 return None 

45 # matplotlib の形はおよそ幅 1 の Y 上向き。画面は Y 下向きなので反転する 

46 def point(x, y): 

47 return QPointF(x * size, -y * size) 

48 

49 painter_path = QPainterPath() 

50 for vertices, code in path.iter_segments(simplify=False, curves=True): 

51 if code == MplPath.MOVETO: 

52 painter_path.moveTo(point(*vertices)) 

53 elif code == MplPath.LINETO: 

54 painter_path.lineTo(point(*vertices)) 

55 elif code == MplPath.CURVE3: 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true

56 painter_path.quadTo(point(*vertices[:2]), point(*vertices[2:])) 

57 elif code == MplPath.CURVE4: 

58 painter_path.cubicTo(point(*vertices[:2]), point(*vertices[2:4]), point(*vertices[4:])) 

59 elif code == MplPath.CLOSEPOLY: 59 ↛ 50line 59 didn't jump to line 50 because the condition on line 59 was always true

60 painter_path.closeSubpath() 

61 return painter_path 

62 

63 

64def _draw_marker(painter, marker, center, size, brush, pen): 

65 path = marker_painter_path(marker, size) 

66 if path is None: 

67 return 

68 painter.save() 

69 painter.translate(center) 

70 painter.setPen(pen) 

71 painter.setBrush(brush) 

72 painter.drawPath(path) 

73 painter.restore() 

74 

75 

76_MARKER_CHOICE_ICON_SIZE = 16 

77# 明暗どちらのテーマの選択肢の背景でも見える中間の灰色 

78_MARKER_CHOICE_ICON_COLOR = '#808080' 

79 

80 

81def marker_choice_icon(marker): 

82 """マーカーの形の選択肢に付ける形の絵。描かない値('None')は None。""" 

83 if marker_painter_path(marker, 1) is None: 

84 return None 

85 side = _MARKER_CHOICE_ICON_SIZE 

86 pixmap = QPixmap(side, side) 

87 pixmap.fill(Qt.GlobalColor.transparent) 

88 painter = QPainter(pixmap) 

89 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 

90 color = QColor(_MARKER_CHOICE_ICON_COLOR) 

91 brush = Qt.BrushStyle.NoBrush if marker_is_unfilled(marker) else QBrush(color) 

92 _draw_marker(painter, marker, QPointF(side / 2, side / 2), side * 0.7, brush, QPen(color, 1.2)) 

93 painter.end() 

94 return QIcon(pixmap) 

95 

96 

97def _marker_brush_and_pen(dataset, color): 

98 """一覧のアイコン用。塗りと枠線の指定を映す(値で色分けする種類も系列の色で代わりに描く)。""" 

99 edge = QColor(dataset.marker_edge_color) if dataset.marker_edge == MARKER_EDGE_COLOR else QColor(color) 

100 edge.setAlphaF(color.alphaF()) 

101 pen = QPen(edge, max(0.0, min(dataset.marker_edge_width, 2.0))) 

102 if dataset.marker_edge_width <= 0: 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true

103 pen = QPen(Qt.PenStyle.NoPen) 

104 if marker_is_unfilled(dataset.marker): 

105 return Qt.BrushStyle.NoBrush, QPen(edge, 1.2) 

106 if dataset.marker_fill == MARKER_FILL_HOLLOW: 

107 return Qt.BrushStyle.NoBrush, pen 

108 if dataset.marker_fill == MARKER_FILL_COLOR: 

109 face = QColor(dataset.marker_face_color) 

110 face.setAlphaF(color.alphaF()) 

111 return QBrush(face), pen 

112 return QBrush(color), pen 

113 

114 

115def make_dataset_style_icon(dataset): 

116 """色・線種・線幅・マーカー・透明度を映した小さな画像。選ばなくても一覧で見分けられるように。""" 

117 width, height = _STYLE_ICON_SIZE 

118 pixmap = QPixmap(width, height) 

119 pixmap.fill(Qt.GlobalColor.transparent) 

120 

121 painter = QPainter(pixmap) 

122 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 

123 

124 color = QColor(dataset.color) 

125 # 透明度が低くても一覧では見えるよう下限を設ける 

126 color.setAlphaF(max(0.2, min(dataset.alpha, 1.0))) 

127 

128 show_line = dataset.plot_type in ('Line', 'Line+Scatter', 'Step') 

129 show_marker = dataset.plot_type in MARKER_PLOT_TYPES 

130 y = height // 2 

131 

132 if show_line: 

133 pen = QPen(color, max(1.0, min(dataset.linewidth, 3.0))) 

134 pen.setStyle(_LINESTYLE_TO_QT_PEN.get(linestyle_name(dataset.linestyle), Qt.PenStyle.SolidLine)) 

135 painter.setPen(pen) 

136 painter.drawLine(2, y, width - 2, y) 

137 

138 if show_marker: 

139 brush, pen = _marker_brush_and_pen(dataset, color) 

140 _draw_marker(painter, dataset.marker, QPointF(width / 2, y), 8, brush, pen) 

141 

142 painter.end() 

143 

144 # Selected モードにも同じ画像を入れる(入れないと Fusion が選択時の画像を作り、塗りつぶした四角になる) 

145 icon = QIcon() 

146 icon.addPixmap(pixmap, QIcon.Mode.Normal) 

147 icon.addPixmap(pixmap, QIcon.Mode.Selected) 

148 return icon 

149 

150 

151# 目の列の固定幅(DATASET_TREE_VISIBILITY_COLUMN_WIDTH)に収まる大きさ 

152_VISIBILITY_ICON_SIZE = 16 

153 

154 

155def make_dataset_visibility_icon(dataset): 

156 name = "eye" if getattr(dataset, "visible", True) else "eye-off" 

157 return _icon_from_svg(name, size=_VISIBILITY_ICON_SIZE) 

158 

159 

160def apply_dataset_visibility_text_style(item, dataset, column=0): 

161 """隠した系列の名前を text_muted の色にする。表示に戻すときは色を消してテーマの文字色に任せる。""" 

162 if getattr(dataset, "visible", True): 

163 item.setData(column, Qt.ItemDataRole.ForegroundRole, None) 

164 else: 

165 from graphica.gui import theme 

166 item.setForeground(column, QBrush(QColor(theme.current_tokens()["text_muted"])))