Added support for zooming graph so that it fits canvas size in Debug Variable Panel
--- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Tue Jun 18 09:55:45 2013 +0200
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Wed Jun 19 22:13:44 2013 +0200
@@ -59,6 +59,46 @@
CURSOR_COLOR = '#800080'
#-------------------------------------------------------------------------------
+# Debug Variable Graphic Viewer Helpers
+#-------------------------------------------------------------------------------
+
+def merge_ranges(ranges):
+ """
+ Merge variables data range in a list to return a range of minimal min range
+ value and maximal max range value extended of 10% for keeping a padding
+ around graph in canvas
+ @param ranges: [(range_min_value, range_max_value),...]
+ @return: merged_range_min_value, merged_range_max_value
+ """
+ # Get minimal and maximal range value
+ min_value = max_value = None
+ for range_min, range_max in ranges:
+ # Update minimal range value
+ if min_value is None:
+ min_value = range_min
+ elif range_min is not None:
+ min_value = min(min_value, range_min)
+
+ # Update maximal range value
+ if max_value is None:
+ max_value = range_max
+ elif range_min is not None:
+ max_value = max(max_value, range_max)
+
+ # Calculate range center and width if at least one valid range is defined
+ if min_value is not None and max_value is not None:
+ center = (min_value + max_value) / 2.
+ range_size = max(1.0, max_value - min_value)
+
+ # Set default center and with if no valid range is defined
+ else:
+ center = 0.5
+ range_size = 1.0
+
+ # Return range expended from 10 %
+ return center - range_size * 0.55, center + range_size * 0.55
+
+#-------------------------------------------------------------------------------
# Debug Variable Graphic Viewer Drop Target
#-------------------------------------------------------------------------------
@@ -224,6 +264,10 @@
# Reference to item for which contextual buttons was displayed
self.ContextualButtonsItem = None
+ # Flag indicating that zoom fit current displayed data range or whole
+ # data range if False
+ self.ZoomFit = False
+
# Create figure for drawing graphs
self.Figure = matplotlib.figure.Figure(facecolor='w')
# Defined border around figure in canvas
@@ -255,6 +299,10 @@
self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
self.mpl_connect('scroll_event', self.OnCanvasScroll)
+ # Add buttons for zooming on current displayed data range
+ self.Buttons.append(
+ GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
+
# Add buttons for changing canvas size with predefined height
for size, bitmap in zip(
[SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
@@ -303,13 +351,34 @@
"""
self.CursorTick = cursor_tick
+ def SetZoomFit(self, zoom_fit):
+ """
+ Set flag indicating that zoom fit current displayed data range
+ @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
+ """
+ # Flag is different from the actual one
+ if zoom_fit != self.ZoomFit:
+ # Save new flag value
+ self.ZoomFit = zoom_fit
+
+ # Update button for zoom fit bitmap
+ self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
+
+ # Refresh canvas
+ self.RefreshViewer()
+
def SubscribeAllDataConsumers(self):
"""
Function that unsubscribe and remove every item that store values of
a variable that doesn't exist in PLC anymore
"""
DebugVariableViewer.SubscribeAllDataConsumers(self)
+
+ # Graph still have data to display
if not self.ItemsIsEmpty():
+ # Reset flag indicating that zoom fit current displayed data range
+ self.SetZoomFit(False)
+
self.ResetGraphics()
def Is3DCanvas(self):
@@ -427,6 +496,13 @@
if item is None
else [item])])
+ def OnZoomFitButton(self):
+ """
+ Function called when Viewer Zoom Fit button is pressed
+ """
+ # Toggle zoom fit flag value
+ self.SetZoomFit(not self.ZoomFit)
+
def GetOnChangeSizeButton(self, height):
"""
Function that generate callback function for change Viewer height to
@@ -494,9 +570,8 @@
# Graph is orthogonal
if self.GraphType == GRAPH_ORTHOGONAL:
# Extract items data displayed in canvas figure
- min_start_tick = max(start_tick, self.GetItemsMinCommonTick())
- start_tick = max(start_tick, min_start_tick)
- end_tick = max(end_tick, min_start_tick)
+ start_tick = max(start_tick, self.GetItemsMinCommonTick())
+ end_tick = max(end_tick, start_tick)
x_data = items[0].GetData(start_tick, end_tick)
y_data = items[1].GetData(start_tick, end_tick)
@@ -769,8 +844,8 @@
KEY_CURSOR_INCREMENT = {
wx.WXK_LEFT: -1,
wx.WXK_RIGHT: 1,
- wx.WXK_UP: -10,
- wx.WXK_DOWN: 10}
+ wx.WXK_UP: 10,
+ wx.WXK_DOWN: -10}
def OnKeyDown(self, event):
"""
@@ -1043,135 +1118,241 @@
self.Figure.subplots_adjust()
def RefreshViewer(self, refresh_graphics=True):
-
+ """
+ Function called to refresh displayed by matplotlib canvas
+ @param refresh_graphics: Flag indicating that graphs have to be
+ refreshed (False: only label values have to be refreshed)
+ """
+ # Refresh graphs if needed
if refresh_graphics:
+ # Get tick range of values to display
start_tick, end_tick = self.ParentWindow.GetRange()
+ # Graph is parallel
if self.GraphType == GRAPH_PARALLEL:
- min_value = max_value = None
-
+ # Init list of data range for each variable displayed
+ ranges = []
+
+ # Get data and range for each variable displayed
for idx, item in enumerate(self.Items):
- data = item.GetData(start_tick, end_tick)
+ data, min_value, max_value = item.GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Check that data is not empty
if data is not None:
- item_min_value, item_max_value = item.GetValueRange()
- if min_value is None:
- min_value = item_min_value
- elif item_min_value is not None:
- min_value = min(min_value, item_min_value)
- if max_value is None:
- max_value = item_max_value
- elif item_max_value is not None:
- max_value = max(max_value, item_max_value)
+ # Add variable range to list of variable data range
+ ranges.append((min_value, max_value))
+ # Add plot to canvas if not yet created
if len(self.Plots) <= idx:
self.Plots.append(
self.Axes.plot(data[:, 0], data[:, 1])[0])
+
+ # Set data to already created plot in canvas
else:
self.Plots[idx].set_data(data[:, 0], data[:, 1])
+
+ # Get X and Y axis ranges
+ x_min, x_max = start_tick, end_tick
+ y_min, y_max = merge_ranges(ranges)
+
+ # Display cursor in canvas if a cursor tick is defined and it is
+ # include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
- if min_value is not None and max_value is not None:
- y_center = (min_value + max_value) / 2.
- y_range = max(1.0, max_value - min_value)
- else:
- y_center = 0.5
- y_range = 1.0
- x_min, x_max = start_tick, end_tick
- y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
-
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
+ # Define a vertical line to display cursor position if no
+ # line is already defined
if self.VLine is None:
- self.VLine = self.Axes.axvline(self.CursorTick, color=CURSOR_COLOR)
+ self.VLine = self.Axes.axvline(self.CursorTick,
+ color=CURSOR_COLOR)
+
+ # Set value of vertical line if already defined
else:
self.VLine.set_xdata((self.CursorTick, self.CursorTick))
self.VLine.set_visible(True)
- else:
- if self.VLine is not None:
- self.VLine.set_visible(False)
+
+ # Hide vertical line if cursor tick is not defined or reset
+ elif self.VLine is not None:
+ self.VLine.set_visible(False)
+
+ # Graph is orthogonal
else:
- min_start_tick = max(start_tick, self.GetItemsMinCommonTick())
- start_tick = max(start_tick, min_start_tick)
- end_tick = max(end_tick, min_start_tick)
+ # Update tick range, removing ticks that don't have a value for
+ # each variable
+ start_tick = max(start_tick, self.GetItemsMinCommonTick())
+ end_tick = max(end_tick, start_tick)
items = self.ItemsDict.values()
- x_data, x_min, x_max = items[0].OrthogonalDataAndRange(start_tick, end_tick)
- y_data, y_min, y_max = items[1].OrthogonalDataAndRange(start_tick, end_tick)
+
+ # Get data and range for first variable (X coordinate)
+ x_data, x_min, x_max = items[0].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+ # Get data and range for second variable (Y coordinate)
+ y_data, y_min, y_max = items[1].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Normalize X and Y coordinates value range
+ x_min, x_max = merge_ranges([(x_min, x_max)])
+ y_min, y_max = merge_ranges([(y_min, y_max)])
+
+ # Get X and Y coordinates for cursor if cursor tick is defined
if self.CursorTick is not None:
- x_cursor, x_forced = items[0].GetValue(self.CursorTick, raw=True)
- y_cursor, y_forced = items[1].GetValue(self.CursorTick, raw=True)
- length = 0
- if x_data is not None and y_data is not None:
- length = min(len(x_data), len(y_data))
+ x_cursor, x_forced = items[0].GetValue(
+ self.CursorTick, raw=True)
+ y_cursor, y_forced = items[1].GetValue(
+ self.CursorTick, raw=True)
+
+ # Get common data length so that each value has an x and y
+ # coordinate
+ length = (min(len(x_data), len(y_data))
+ if x_data is not None and y_data is not None
+ else 0)
+
+ # Graph is orthogonal 2D
if len(self.Items) < 3:
+
+ # Check that x and y data are not empty
if x_data is not None and y_data is not None:
+
+ # Add plot to canvas if not yet created
if len(self.Plots) == 0:
self.Plots.append(
self.Axes.plot(x_data[:, 1][:length],
y_data[:, 1][:length])[0])
+
+ # Set data to already created plot in canvas
else:
self.Plots[0].set_data(
x_data[:, 1][:length],
y_data[:, 1][:length])
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
+ # Display cursor in canvas if a cursor tick is defined and it is
+ # include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
+
+ # Define a vertical line to display cursor x coordinate
+ # if no line is already defined
if self.VLine is None:
- self.VLine = self.Axes.axvline(x_cursor, color=CURSOR_COLOR)
+ self.VLine = self.Axes.axvline(x_cursor,
+ color=CURSOR_COLOR)
+ # Set value of vertical line if already defined
else:
self.VLine.set_xdata((x_cursor, x_cursor))
+
+
+ # Define a horizontal line to display cursor y
+ # coordinate if no line is already defined
if self.HLine is None:
- self.HLine = self.Axes.axhline(y_cursor, color=CURSOR_COLOR)
+ self.HLine = self.Axes.axhline(y_cursor,
+ color=CURSOR_COLOR)
+ # Set value of horizontal line if already defined
else:
self.HLine.set_ydata((y_cursor, y_cursor))
+
self.VLine.set_visible(True)
self.HLine.set_visible(True)
+
+ # Hide vertical and horizontal line if cursor tick is not
+ # defined or reset
else:
if self.VLine is not None:
self.VLine.set_visible(False)
if self.HLine is not None:
self.HLine.set_visible(False)
+
+ # Graph is orthogonal 3D
else:
+ # Remove all plots already defined in 3D canvas
while len(self.Axes.lines) > 0:
self.Axes.lines.pop()
- z_data, z_min, z_max = items[2].OrthogonalDataAndRange(start_tick, end_tick)
- if self.CursorTick is not None:
- z_cursor, z_forced = items[2].GetValue(self.CursorTick, raw=True)
- if x_data is not None and y_data is not None and z_data is not None:
+
+ # Get data and range for third variable (Z coordinate)
+ z_data, z_min, z_max = items[2].GetDataAndValueRange(
+ start_tick, end_tick, not self.ZoomFit)
+
+ # Normalize Z coordinate value range
+ z_min, z_max = merge_ranges([(z_min, z_max)])
+
+ # Check that x, y and z data are not empty
+ if (x_data is not None and y_data is not None and
+ z_data is not None):
+
+ # Get common data length so that each value has an x, y
+ # and z coordinate
length = min(length, len(z_data))
+
+ # Add plot to canvas
self.Axes.plot(x_data[:, 1][:length],
y_data[:, 1][:length],
zs = z_data[:, 1][:length])
- self.Axes.set_zlim(z_min, z_max)
- if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
+
+ # Display cursor in canvas if a cursor tick is defined and
+ # it is include in values tick range
+ if (self.CursorTick is not None and
+ start_tick <= self.CursorTick <= end_tick):
+
+ # Get Z coordinate for cursor
+ z_cursor, z_forced = items[2].GetValue(
+ self.CursorTick, raw=True)
+
+ # Add 3 lines parallel to x, y and z axis to display
+ # cursor position in 3D
for kwargs in [{"xs": numpy.array([x_min, x_max])},
{"ys": numpy.array([y_min, y_max])},
{"zs": numpy.array([z_min, z_max])}]:
- for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
- ("ys", numpy.array([y_cursor, y_cursor])),
- ("zs", numpy.array([z_cursor, z_cursor]))]:
+ for param, value in [
+ ("xs", numpy.array([x_cursor, x_cursor])),
+ ("ys", numpy.array([y_cursor, y_cursor])),
+ ("zs", numpy.array([z_cursor, z_cursor]))]:
kwargs.setdefault(param, value)
kwargs["color"] = CURSOR_COLOR
self.Axes.plot(**kwargs)
-
+
+ # Set Z axis limits
+ self.Axes.set_zlim(z_min, z_max)
+
+ # Set X and Y axis limits
self.Axes.set_xlim(x_min, x_max)
self.Axes.set_ylim(y_min, y_max)
- variable_name_mask = self.ParentWindow.GetVariableNameMask()
- if self.CursorTick is not None:
- values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
- else:
- values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
- labels = [item.GetVariable(variable_name_mask) for item in self.Items]
+ # Get value and forced flag for each variable displayed in graph
+ # If cursor tick is not defined get value and flag of last received
+ # or get value and flag of variable at cursor tick
+ values, forced = apply(zip, [
+ (item.GetValue(self.CursorTick)
+ if self.CursorTick is not None
+ else (item.GetValue(), item.IsForced()))
+ for item in self.Items])
+
+ # Get path of each variable displayed simplified using panel variable
+ # name mask
+ labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask())
+ for item in self.Items]
+
+ # Get style for each variable according to
styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
+
+ # Graph is orthogonal 3D, set variables path as 3D axis label
if self.Is3DCanvas():
for idx, label_func in enumerate([self.Axes.set_xlabel,
self.Axes.set_ylabel,
self.Axes.set_zlabel]):
- label_func(labels[idx], fontdict={'size': 'small','color': COLOR_CYCLE[idx]})
+ label_func(labels[idx], fontdict={'size': 'small',
+ 'color': COLOR_CYCLE[idx]})
+
+ # Graph is not orthogonal 3D, set variables path in axes labels
else:
for label, text in zip(self.AxesLabels, labels):
label.set_text(text)
+
+ # Set value label text and style according to value and forced flag for
+ # each variable displayed
for label, value, style in zip(self.Labels, values, styles):
label.set_text(value)
label.set_style(style)
+ # Refresh figure
self.draw()
def draw(self, drawDC=None):
--- a/controls/DebugVariablePanel/DebugVariableItem.py Tue Jun 18 09:55:45 2013 +0200
+++ b/controls/DebugVariablePanel/DebugVariableItem.py Wed Jun 19 22:13:44 2013 +0200
@@ -172,24 +172,33 @@
"""
return self.MinValue, self.MaxValue
- def OrthogonalDataAndRange(self, start_tick, end_tick):
- """
- Return variable value range
+ def GetDataAndValueRange(self, start_tick, end_tick, full_range=True):
+ """
+ Return variable data and value range for a given tick range
@param start_tick: Start tick of given range (default None, first data)
@param end_tick: end tick of given range (default None, last data)
+ @param full_range: Value range is calculated on whole data (False: only
+ calculated on data in given range)
@return: (numpy.array([(tick, value, forced),...]),
min_value, max_value)
"""
- # Calculate min_value and max_value so that range size is greater
- # than 1.0
- if self.MinValue is not None and self.MaxValue is not None:
- center = (self.MinValue + self.MaxValue) / 2.
- range = max(1.0, self.MaxValue - self.MinValue)
- else:
- center = 0.5
- range = 1.0
- return (self.GetData(start_tick, end_tick),
- center - range * 0.55, center + range * 0.55)
+ # Get data in given tick range
+ data = self.GetData(start_tick, end_tick)
+
+ # Value range is calculated on whole data
+ if full_range:
+ return data, self.MinValue, self.MaxValue
+
+ # Check that data in given range is not empty
+ values = data[:, 1]
+ if len(values) > 0:
+ # Return value range for data in given tick range
+ return (data,
+ data[numpy.argmin(values), 1],
+ data[numpy.argmax(values), 1])
+
+ # Return default values
+ return data, None, None
def ResetData(self):
"""
--- a/controls/DebugVariablePanel/GraphButton.py Tue Jun 18 09:55:45 2013 +0200
+++ b/controls/DebugVariablePanel/GraphButton.py Wed Jun 19 22:13:44 2013 +0200
@@ -46,8 +46,8 @@
"""
# Save button position
self.SetPosition(x, y)
- # Get wx.Bitmap object corresponding to bitmap
- self.Bitmap = GetBitmap(bitmap)
+ # Set button bitmap
+ self.SetBitmap(bitmap)
# By default button is hide and enabled
self.Shown = False
@@ -63,6 +63,14 @@
# Remove reference to callback function
self.callback = None
+ def SetBitmap(self, bitmap):
+ """
+ Set bitmap to use for button
+ @param bitmap: Name of bitmap to use for button
+ """
+ # Get wx.Bitmap object corresponding to bitmap
+ self.Bitmap = GetBitmap(bitmap)
+
def GetSize(self):
"""
Return size of button
Binary file images/fit_graph.png has changed
Binary file images/full_graph.png has changed
--- a/images/plcopen_icons.svg Tue Jun 18 09:55:45 2013 +0200
+++ b/images/plcopen_icons.svg Wed Jun 19 22:13:44 2013 +0200
@@ -11114,6 +11114,318 @@
id="radialGradient5955"
xlink:href="#radialGradient4208-2"
inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5533-1"
+ y2="609.51001"
+ gradientUnits="userSpaceOnUse"
+ x2="302.85999"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.77854,15.669)"
+ y1="366.64999"
+ x1="302.85999">
+ <stop
+ id="stop5050-7-1"
+ style="stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056-19-2"
+ offset=".5" />
+ <stop
+ id="stop5052-4-7"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5535-3"
+ xlink:href="#linearGradient5060-1-4"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.78465,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5060-1-4">
+ <stop
+ id="stop5062-7-0"
+ offset="0" />
+ <stop
+ id="stop5064-9-6"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5537-8"
+ xlink:href="#linearGradient5060-1-4"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(-0.031048,0,0,0.013668,23.215,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5891">
+ <stop
+ id="stop5893"
+ offset="0" />
+ <stop
+ id="stop5895-3"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5565-0"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.63636,0,0,0.62295,-3.9091,-3.1066)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-4"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-2"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-4"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5562-5-4"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.61291,0,0,0.58621,-3.3226,-2.069)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-0"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-8"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5559-2"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.52632,0,0,0.48148,-0.63158,1.7407)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-5"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-67"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6639-4"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,-1,0,25.121,-26.636)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient6388-8">
+ <stop
+ id="stop6390-9"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop6392-8"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6643-1"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,-1,-1,0,25.121,55.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient5912">
+ <stop
+ id="stop5914"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5916"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6647-7"
+ y2="22.839001"
+ xlink:href="#linearGradient6388-8"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,1,0,-1.1213,-26.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient5919">
+ <stop
+ id="stop5921"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5923"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="22.839001"
+ x2="33.25"
+ y1="16.121"
+ x1="39.879002"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3099-5-7"
+ xlink:href="#linearGradient6388-8"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5926">
+ <stop
+ id="stop5928"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop5930-6"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="22.839001"
+ x2="33.25"
+ y1="16.121"
+ x1="39.879002"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5947"
+ xlink:href="#linearGradient6388-8"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5528-2"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-0"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-1"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-8"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5525-2"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-5"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-7"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5522-2"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-3"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-4"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5528-5"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783-8"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301-3"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785-3"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5525-31"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692-48"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694-10"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5522-7"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459-7"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461-7"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
</defs>
<sodipodi:namedview
id="base"
@@ -11122,9 +11434,9 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
- inkscape:zoom="2.828427"
- inkscape:cx="60.957367"
- inkscape:cy="-299.13955"
+ inkscape:zoom="7.9999995"
+ inkscape:cx="103.60893"
+ inkscape:cy="-243.21864"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="16px"
@@ -16155,5 +16467,161 @@
d="M 18.531,8.7812 V 10 A 0.51754,0.51754 0 0 1 18,10.531 H 9.4375 l 0.03125,2.9375 h 8.5312 a 0.51754,0.51754 0 0 1 0.531,0.532 v 1.1562 l 3.469,-3.281 -3.469,-3.0938 z"
transform="translate(0,0.99987)" />
</g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="70.198586"
+ y="255.80313"
+ id="text3638-3-3-2-0-9-8-5-5-2-6"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9-5-4"
+ x="70.198586"
+ y="255.80313">%%fit_graph%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="259.125"
+ x="80.25"
+ height="16"
+ width="16"
+ id="fit_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="108.19859"
+ y="255.80313"
+ id="text3638-3-3-2-0-9-8-5-5-2-6-9"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9-5-4-0"
+ x="108.19859"
+ y="255.80313">%%full_graph%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="259.125"
+ x="119.84098"
+ height="16"
+ width="16"
+ id="full_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <g
+ transform="translate(119.73268,258.52291)"
+ id="g5512">
+ <rect
+ id="rect1887-08"
+ style="fill:url(#linearGradient5528-2);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+ height="14"
+ width="15"
+ y="1.5"
+ x="0.49996999" />
+ <rect
+ id="rect2779-5"
+ style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-2);stroke-width:1.00010002"
+ height="12"
+ width="13"
+ y="2.5000999"
+ x="1.5001" />
+ <rect
+ id="rect6287-2"
+ style="fill:url(#linearGradient5522-2)"
+ height="9"
+ width="14"
+ y="6"
+ x="1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6293-3"
+ style="fill:#ffc24c"
+ d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect5590-5"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 5.5355,13.293 2.7071,10.464 2,14 5.5355,13.293 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6302"
+ style="fill:#ffc24c"
+ d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6304"
+ style="fill:#ffc24c"
+ d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6312"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 5.5355,7.7071 2.7071,10.536 2,7 5.5355,7.7071 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6316"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 10.464,13.293 13.293,10.464 14,14 10.464,13.293 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6318"
+ style="opacity:0.7;fill:#aa0000"
+ d="M 10.464,7.7071 13.293,10.536 14,7 10.464,7.7071 z" />
+ </g>
+ <g
+ transform="translate(80.18127,258.41201)"
+ id="g5498-7">
+ <rect
+ id="rect1887-3"
+ style="fill:url(#linearGradient5528-5);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+ height="14"
+ width="15"
+ y="1.5"
+ x="0.49996999" />
+ <rect
+ id="rect2779-33"
+ style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-31);stroke-width:1.00010002"
+ height="12"
+ width="13"
+ y="2.5000999"
+ x="1.5001" />
+ <rect
+ id="rect6287-0"
+ style="fill:url(#linearGradient5522-7)"
+ height="9"
+ width="14"
+ y="6"
+ x="1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6293-8"
+ style="fill:#ffc24c"
+ d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect5590-8"
+ style="fill:#73a300"
+ d="M 2.4645,11.707 5.2929,14.536 6,11 2.4645,11.707 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6302-0"
+ style="fill:#ffc24c"
+ d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6304-7"
+ style="fill:#ffc24c"
+ d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6312-0"
+ style="fill:#73a300"
+ d="M 2.4645,9.2929 5.2929,6.4645 6,10 2.4645,9.2929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6316-1"
+ style="fill:#73a300"
+ d="M 13.536,11.707 10.707,14.536 10,11 l 3.5355,0.70711 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6318-5"
+ style="fill:#73a300"
+ d="M 13.536,9.2929 10.707,6.4645 10,10 13.536,9.2929 z" />
+ </g>
</g>
</svg>