8000 Merge pull request #26064 from saranti/fishbone · matplotlib/matplotlib@053e529 · GitHub
[go: up one dir, main page]

Skip to content

Commit 053e529

Browse files
authored
Merge pull request #26064 from saranti/fishbone
add ishikawa diagram to examples
2 parents fc350ea + ae97baf commit 053e529

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""
2+
================
3+
Ishikawa Diagram
4+
================
5+
6+
Ishikawa Diagrams, fishbone diagrams, herringbone diagrams, or cause-and-effect
7+
diagrams are used to identify problems in a system by showing how causes and
8+
effects are linked.
9+
Source: https://en.wikipedia.org/wiki/Ishikawa_diagram
10+
11+
"""
12+
import matplotlib.pyplot as plt
13+
14+
from matplotlib.patches import Polygon, Wedge
15+
16+
# Create the fishbone diagram
17+
fig, ax = plt.subplots(figsize=(10, 6), layout='constrained')
18+
ax.set_xlim(-5, 5)
19+
ax.set_ylim(-5, 5)
20+
ax.axis('off')
21+
22+
23+
def problems(data: str,
24+
problem_x: float, problem_y: float,
25+
prob_angle_x: float, prob_angle_y: float):
26+
"""
27+
Draw each problem section of the Ishikawa plot.
28+
29+
Parameters
30+
----------
31+
data : str
32+
The category name.
33+
problem_x, problem_y : float, optional
34+
The `X` and `Y` positions of the problem arrows (`Y` defaults to zero).
35+
prob_angle_x, prob_angle_y : float, optional
36+
The angle of the problem annotations. They are angled towards
37+
the tail of the plot.
38+
39+
Returns
40+
-------
41+
None.
42+
43+
"""
44+
ax.annotate(str.upper(data), xy=(problem_x, problem_y),
45+
xytext=(prob_angle_x, prob_angle_y),
46+
fontsize='10',
47+
color='white',
48+
weight='bold',
49+
xycoords='data',
50+
verticalalignment='center',
51+
horizontalalignment='center',
52+
textcoords='offset fontsize',
53+
arrowprops=dict(arrowstyle="->", facecolor='black'),
54+
bbox=dict(boxstyle='square',
55+
facecolor='tab:blue',
56+
pad=0.8))
57+
58+
59+
def causes(data: list, cause_x: float, cause_y: float,
60+
cause_xytext=(-9, -0.3), top: bool = True):
61+
"""
62+
Place each cause to a position relative to the problems
63+
annotations.
64+
65+
Parameters
66+
----------
67+
data : indexable object
68+
The input data. IndexError is
69+
raised if more than six arguments are passed.
70+
cause_x, cause_y : float
71+
The `X` and `Y` position of the cause annotations.
72+
cause_xytext : tuple, optional
73+
Adjust to set the distance of the cause text from the problem
74+
arrow in fontsize units.
75+
top : bool
76+
77+
Returns
78+
-------
79+
None.
80+
81+
"""
82+
for index, cause in enumerate(data):
83+
# First cause annotation is placed in the middle of the problems arrow
84+
# and each subsequent cause is plotted above or below it in succession.
85+
86+
# [<x pos>, [<y pos top>, <y pos bottom>]]
87+
coords = [[0, [0, 0]],
88+
[0.23, [0.5, -0.5]],
89+
[-0.46, [-1, 1]],
90+
[0.69, [1.5, -1.5]],
91+
[-0.92, [-2, 2]],
92+
[1.15, [2.5, -2.5]]]
93+
if top:
94+
cause_y += coords[index][1][0]
95+
else:
96+
cause_y += coords[index][1][1]
97+
cause_x -= coords[index][0]
98+
99+
ax.annotate(cause, xy=(cause_x, cause_y),
100+
horizontalalignment='center',
101+
xytext=cause_xytext,
102+
fontsize='9',
103+
xycoords='data',
104+
textcoords='offset fontsize',
105+
arrowprops=dict(arrowstyle="->",
106+
facecolor='black'))
107+
108+
109+
def draw_body(data: dict):
110+
"""
111+
Place each section in its correct place by changing
112+
the coordinates on each loop.
113+
114+
Parameters
115+
----------
116+
data : dict
117+
The input data (can be list or tuple). ValueError is
118+
raised if more than six arguments are passed.
119+
120+
Returns
121+
-------
122+
None.
123+
124+
"""
125+
second_sections = []
126+
third_sections = []
127+
# Resize diagram to automatically scale in response to the number
128+
# of problems in the input data.
129+
if len(data) == 1 or len(data) == 2:
130+
spine_length = (-2.1, 2)
131+
head_pos = (2, 0)
132+
tail_pos = ((-2.8, 0.8), (-2.8, -0.8), (-2.0, -0.01))
133+
first_section = [1.6, 0.8]
134+
elif len(data) == 3 or len(data) == 4:
135+
spine_length = (-3.1, 3)
136+
head_pos = (3, 0)
137+
tail_pos = ((-3.8, 0.8), (-3.8, -0.8), (-3.0, -0.01))
138+
first_section = [2.6, 1.8]
139+
second_sections = [-0.4, -1.2]
140+
else: # len(data) == 5 or 6
141+
spine_length = (-4.1, 4)
142+
head_pos = (4, 0)
143+
tail_pos = ((-4.8, 0.8), (-4.8, -0.8), (-4.0, -0.01))
144+
first_section = [3.5, 2.7]
145+
second_sections = [1, 0.2]
146+
third_sections = [-1.5, -2.3]
147+
148+
# Change the coordinates of the annotations on each loop.
149+
for index, problem in enumerate(data.values()):
150+
top_row = True
151+
cause_arrow_y = 1.7
152+
if index % 2 != 0: # Plot problems below the spine.
153+
top_row = False
154+
y_prob_angle = -16
155+
cause_arrow_y = -1.7
156+
else: # Plot problems above the spine.
157+
y_prob_angle = 16
158+
# Plot the 3 sections in pairs along the main spine.
159+
if index in (0, 1):
160+
prob_arrow_x = first_section[0]
161+
cause_arrow_x = first_section[1]
162+
elif index in (2, 3):
163+
prob_arrow_x = second_sections[0]
164+
cause_arrow_x = second_sections[1]
165+
else:
166+
prob_arrow_x = third_sections[0]
167+
cause_arrow_x = third_sections[1]
168+
if index > 5:
169+
raise ValueError(f'Maximum number of problems is 6, you have entered '
170+
f'{len(data)}')
171+
172+
# draw main spine
173+
ax.plot(spine_length, [0, 0], color='tab:blue', linewidth=2)
174+
# draw fish head
175+
ax.text(head_pos[0] + 0.1, head_pos[1] - 0.05, 'PROBLEM', fontsize=10,
176+
weight='bold', color='white')
177+
semicircle = Wedge(head_pos, 1, 270, 90, fc='tab:blue')
178+
ax.add_patch(semicircle)
179+
# draw fishtail
180+
triangle = Polygon(tail_pos, fc='tab:blue')
181+
ax.add_patch(triangle)
182+
# Pass each category name to the problems function as a string on each loop.
183+
problems(list(data.keys())[index], prob_arrow_x, 0, -12, y_prob_angle)
184+
# Start the cause function with the first annotation being plotted at
185+
# the cause_arrow_x, cause_arrow_y coordinates.
186+
causes(problem, cause_arrow_x, cause_arrow_y, top=top_row)
187+
188+
189+
# Input data
190+
categories = {
191+
'Method': ['Time consumption', 'Cost', 'Procedures', 'Inefficient process',
192+
'Sampling'],
193+
'Machine': ['Faulty equipment', 'Compatibility'],
194+
'Material': ['Poor-quality input', 'Raw materials', 'Supplier',
195+
'Shortage'],
196+
'Measurement': ['Calibration', 'Performance', 'Wrong measurements'],
197+
'Environment': ['Bad conditions'],
198+
'People': ['Lack of training', 'Managers', 'Labor shortage',
199+
'Procedures', 'Sales strategy']
200+
}
201+
202+
draw_body(categories)
203+
plt.show()

0 commit comments

Comments
 (0)
0