Skip to content

Commit b0341ee

Browse files
authored
Fix tick label font editing (#88)
* Fix tick label font editing * Address tick font review feedback * Tighten tick font change detection
1 parent b6be919 commit b0341ee

2 files changed

Lines changed: 242 additions & 8 deletions

File tree

pylustrator/components/qitem_properties.py

Lines changed: 179 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ def __init__(self, layout: QtWidgets.QLayout):
307307
]
308308

309309
self.properties = {}
310+
self.changed_property_names = set()
310311
self.propertiesChanged.connect(
311312
lambda: self.target
312313
and main_figure(
@@ -354,15 +355,19 @@ def selectFont(self):
354355
font, x = QtWidgets.QFontDialog.getFont(font0, self)
355356

356357
self.properties["fontname"] = font.family()
358+
self.changed_property_names.add("fontname")
357359
if font.weight() != font0.weight():
358360
self.properties["fontweight"] = self.convertQtWeightToMplWeight(
359361
font.weight()
360362
)
363+
self.changed_property_names.add("fontweight")
361364
if font.pointSizeF() != font0.pointSizeF():
362365
self.properties["fontsize"] = font.pointSizeF()
366+
self.changed_property_names.add("fontsize")
363367
if font.italic() != font0.italic():
364368
style = "italic" if font.italic() else "normal"
365369
self.properties["fontstyle"] = style
370+
self.changed_property_names.add("fontstyle")
366371

367372
self.propertiesChanged.emit()
368373
# main_figure(self.target).canvas.draw()
@@ -401,6 +406,7 @@ def setTarget(self, element: Artist | List[Artist]):
401406
value = getattr(current_element, "get_" + name2)()
402407
self.properties[name] = value
403408

409+
self.changed_property_names = set()
404410
self.target = current_element
405411
finally:
406412
self.noSignal = False
@@ -417,28 +423,33 @@ def delete(self):
417423
def changeWeight(self, checked: bool):
418424
"""set bold or normal"""
419425
self.properties["fontweight"] = "bold" if checked else "normal"
426+
self.changed_property_names.add("fontweight")
420427
self.propertiesChanged.emit()
421428

422429
def changeStyle(self, checked: bool):
423430
"""set italic or normal"""
424431
self.properties["fontstyle"] = "italic" if checked else "normal"
432+
self.changed_property_names.add("fontstyle")
425433
self.propertiesChanged.emit()
426434

427435
def changeColor(self, color: str):
428436
"""set the text color"""
429437
self.properties["color"] = color
438+
self.changed_property_names.add("color")
430439
self.propertiesChanged.emit()
431440

432441
def changeAlign(self, align: str):
433442
"""set the text algin"""
434443
self.properties["horizontalalignment"] = align
444+
self.changed_property_names.add("horizontalalignment")
435445
self.propertiesChanged.emit()
436446

437447
def changeFontSize(self, value: int):
438448
"""set the font size"""
439449
if self.noSignal:
440450
return
441451
self.properties["fontsize"] = value
452+
self.changed_property_names.add("fontsize")
442453
self.propertiesChanged.emit()
443454

444455

@@ -946,6 +957,44 @@ def getFontProperties(self):
946957
prop_copy2[name] = value
947958
return (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())), prop_copy2
948959

960+
def tickLabelFontChanged(self, element: Axes, properties: dict):
961+
"""check if the current major tick labels differ from the requested font"""
962+
for label in getattr(element, "get_" + self.axis + "ticklabels")():
963+
for name, value in properties.items():
964+
getter = getattr(label, "get_" + name)
965+
if getter() != value:
966+
return True
967+
return False
968+
969+
def getChangedFontProperties(self, element: Axes):
970+
labels = getattr(element, "get_" + self.axis + "ticklabels")()
971+
if len(labels) == 0:
972+
return {}
973+
changed_properties = {}
974+
changed_property_names = self.input_font.changed_property_names
975+
if not changed_property_names:
976+
return {}
977+
for name, name2, type_, default_ in self.input_font.property_names:
978+
if (
979+
name not in changed_property_names
980+
or name not in self.input_font.properties
981+
):
982+
continue
983+
value = self.input_font.properties[name]
984+
if any(getattr(label, "get_" + name)() != value for label in labels):
985+
changed_properties[name] = value
986+
987+
return changed_properties
988+
989+
def getFontPropertyString(self, properties: dict):
990+
prop_copy = {}
991+
for name, value in properties.items():
992+
if isinstance(value, str):
993+
prop_copy[name] = '"' + value + '"'
994+
else:
995+
prop_copy[name] = value
996+
return ", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())
997+
949998
def fontStateChanged(self):
950999
self.ticksChanged()
9511000
# fig.change_tracker.addChange(axes, ".legend(%s)" % (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())))
@@ -975,9 +1024,12 @@ def ticksChanged(self):
9751024
for element in main_figure(self.element).selection.targets
9761025
if element.target != self.element and isinstance(element.target, Axes)
9771026
]
1027+
changed_font_properties = [
1028+
self.getChangedFontProperties(element) for element in elements
1029+
]
9781030

9791031
changed = False
980-
for elem in elements:
1032+
for elem, font_properties in zip(elements, changed_font_properties):
9811033
current_ticks = getattr(elem, "get_" + self.axis + "ticks")()
9821034
current_ticklabels = [
9831035
t.get_text() for t in getattr(elem, "get_" + self.axis + "ticklabels")()
@@ -987,19 +1039,138 @@ def ticksChanged(self):
9871039
or (current_ticks != ticks).any()
9881040
or len(current_ticklabels) != len(labels)
9891041
or current_ticklabels != labels
1042+
or bool(font_properties)
9901043
):
9911044
changed = True
9921045
if changed is False:
9931046
return
9941047

995-
with UndoRedo(elements, "Axes Ticks"):
996-
for element in elements:
997-
kwargs = {}
998-
kwargs[f"{self.axis}ticks"] = ticks
999-
kwargs[f"{self.axis}ticklabels"] = labels
1000-
kwargs[f"{self.axis}lim"] = self.range
1001-
element.set(**kwargs)
1048+
if not any(changed_font_properties):
1049+
with UndoRedo(elements, "Axes Ticks"):
1050+
for element in elements:
1051+
kwargs = {}
1052+
kwargs[f"{self.axis}ticks"] = ticks
1053+
kwargs[f"{self.axis}ticklabels"] = labels
1054+
kwargs[f"{self.axis}lim"] = self.range
1055+
element.set(**kwargs)
1056+
return
10021057

1058+
old_properties = []
1059+
for element in elements:
1060+
axis = getattr(element, "get_" + self.axis + "axis")()
1061+
ticklabels = [
1062+
t.get_text()
1063+
for t in getattr(element, "get_" + self.axis + "ticklabels")()
1064+
]
1065+
ticklabel_properties = [
1066+
{
1067+
name: getattr(t, "get_" + name)()
1068+
for name, name2, type_, default_ in self.input_font.property_names
1069+
}
1070+
for t in getattr(element, "get_" + self.axis + "ticklabels")()
1071+
]
1072+
old_properties.append(
1073+
[
1074+
axis.major.locator,
1075+
axis.major.formatter,
1076+
getattr(element, "get_" + self.axis + "lim")(),
1077+
getattr(element, "get_" + self.axis + "ticks")(),
1078+
ticklabels,
1079+
ticklabel_properties,
1080+
]
1081+
)
1082+
1083+
def undo():
1084+
for element, properties in zip(elements, old_properties):
1085+
(
1086+
locator,
1087+
formatter,
1088+
lim,
1089+
old_ticks,
1090+
old_labels,
1091+
old_label_properties,
1092+
) = properties
1093+
axis = getattr(element, "get_" + self.axis + "axis")()
1094+
current_ticks = getattr(element, "get_" + self.axis + "ticks")()
1095+
current_ticklabels = [
1096+
t.get_text()
1097+
for t in getattr(element, "get_" + self.axis + "ticklabels")()
1098+
]
1099+
font_only = (
1100+
len(current_ticks) == len(old_ticks)
1101+
and (current_ticks == old_ticks).all()
1102+
and len(current_ticklabels) == len(old_labels)
1103+
and current_ticklabels == old_labels
1104+
)
1105+
if font_only:
1106+
old_texts = getattr(element, "get_" + self.axis + "ticklabels")()
1107+
else:
1108+
getattr(element, "set_" + self.axis + "ticks")(old_ticks)
1109+
old_texts = getattr(element, "set_" + self.axis + "ticklabels")(
1110+
old_labels
1111+
)
1112+
for text, properties in zip(old_texts, old_label_properties):
1113+
text.update(properties)
1114+
getattr(element, "set_" + self.axis + "lim")(lim)
1115+
axis.set_major_locator(locator)
1116+
axis.set_major_formatter(formatter)
1117+
self.fig.change_tracker.addNewAxesChange(element)
1118+
1119+
def redo():
1120+
for element, properties in zip(elements, changed_font_properties):
1121+
current_ticks = getattr(element, "get_" + self.axis + "ticks")()
1122+
current_ticklabels = [
1123+
t.get_text()
1124+
for t in getattr(element, "get_" + self.axis + "ticklabels")()
1125+
]
1126+
font_only = (
1127+
len(current_ticks) == len(ticks)
1128+
and (current_ticks == ticks).all()
1129+
and len(current_ticklabels) == len(labels)
1130+
and current_ticklabels == labels
1131+
)
1132+
if font_only and not properties:
1133+
continue
1134+
if font_only:
1135+
for text in getattr(element, "get_" + self.axis + "ticklabels")():
1136+
text.update(properties)
1137+
else:
1138+
getattr(element, "set_" + self.axis + "ticks")(ticks)
1139+
getattr(element, "set_" + self.axis + "ticklabels")(
1140+
labels, **properties
1141+
)
1142+
getattr(element, "set_" + self.axis + "lim")(self.range)
1143+
min, max = getattr(element, "get_" + self.axis + "lim")()
1144+
if min != self.range[0] or max != self.range[1]:
1145+
self.fig.change_tracker.addChange(
1146+
element,
1147+
".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)),
1148+
)
1149+
else:
1150+
self.fig.change_tracker.addChange(
1151+
element,
1152+
".set_"
1153+
+ self.axis
1154+
+ "lim(%s, %s)" % (str(self.range[0]), str(self.range[1])),
1155+
)
1156+
1157+
self.fig.change_tracker.addChange(
1158+
element,
1159+
".set_" + self.axis + "ticks([%s], [%s]%s)"
1160+
% (
1161+
", ".join(self.str(t) for t in ticks),
1162+
", ".join('"' + label + '"' for label in labels),
1163+
(
1164+
", " + self.getFontPropertyString(properties)
1165+
if properties
1166+
else ""
1167+
),
1168+
),
1169+
)
1170+
1171+
self.fig.change_tracker.addEdit([undo, redo, "ticks"])
1172+
redo()
1173+
self.fig.canvas.draw()
10031174
return
10041175
if 0:
10051176
getattr(element, "set_" + self.axis + "lim")(self.range)

tests/test_axes.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,69 @@ def set_ylog(_):
312312
"yscale", "log", set_ylog, get_axes, line_command, test_run
313313
)
314314

315+
def test_axis_tick_label_font(self):
316+
fig, text = self.run_plot_script()
317+
318+
def get_axes():
319+
return fig.axes[0]
320+
321+
test_run = "Change axes tick label font."
322+
line_command = "plt.figure(1).axes[0].set_xticks("
323+
324+
def set_tick_font(_):
325+
tick_edit = self.fig.window.input_properties.input_xaxis.tick_edit
326+
tick_edit.setTarget(get_axes())
327+
tick_edit.input_font.properties["fontname"] = "Times New Roman"
328+
tick_edit.input_font.properties["fontsize"] = 14
329+
tick_edit.input_font.properties["fontweight"] = "bold"
330+
tick_edit.input_font.properties["fontstyle"] = "italic"
331+
tick_edit.input_font.changed_property_names.update(
332+
["fontname", "fontsize", "fontweight", "fontstyle"]
333+
)
334+
tick_edit.input_font.propertiesChanged.emit()
335+
336+
def get_tick_fonts():
337+
label = get_axes().get_xticklabels()[0]
338+
return [
339+
str(label.get_fontsize()),
340+
label.get_fontweight(),
341+
label.get_fontstyle(),
342+
]
343+
344+
def check_saved_property():
345+
line, (args, kwargs) = self.check_line_in_file(line_command)
346+
self.assertEqual(
347+
kwargs["fontname"],
348+
"Times New Roman",
349+
f"Property 'fontname' not saved correctly. [{test_run}]",
350+
)
351+
self.assertEqual(
352+
kwargs["fontsize"],
353+
14,
354+
f"Property 'fontsize' not saved correctly. [{test_run}]",
355+
)
356+
self.assertEqual(
357+
kwargs["fontweight"],
358+
"bold",
359+
f"Property 'fontweight' not saved correctly. [{test_run}]",
360+
)
361+
self.assertEqual(
362+
kwargs["fontstyle"],
363+
"italic",
364+
f"Property 'fontstyle' not saved correctly. [{test_run}]",
365+
)
366+
367+
self.change_property(
368+
"fontsize",
369+
["14.0", "bold", "italic"],
370+
set_tick_font,
371+
get_axes,
372+
line_command,
373+
test_run,
374+
get_function=get_tick_fonts,
375+
test_saved_value=check_saved_property,
376+
)
377+
315378
def test_minor_axis_ticks(self):
316379
# get the figure
317380
fig, text = self.run_plot_script()

0 commit comments

Comments
 (0)