diff --git a/eos/graph/__init__.py b/eos/graph/__init__.py
index a5fd6c72b..77a348e88 100644
--- a/eos/graph/__init__.py
+++ b/eos/graph/__init__.py
@@ -17,107 +17,35 @@
# along with eos. If not, see .
# ===============================================================================
-import itertools
+
+from abc import ABCMeta, abstractmethod
-class Graph:
+class Graph(metaclass=ABCMeta):
- def __init__(self, fit, function, data=None):
- self.fit = fit
- self.data = {}
- if data is not None:
- for name, d in data.items():
- self.setData(Data(name, d))
+ def __init__(self):
+ self.cache = {}
- self.function = function
+ @abstractmethod
+ def getPlotPoints(self, fit, extraData, xRange, xAmount):
+ raise NotImplementedError
- def clearData(self):
- self.data.clear()
+ def getYForX(self, fit, extraData, x):
+ raise NotImplementedError
- def setData(self, data):
- self.data[data.name] = data
-
- def getIterator(self):
- pointNames = []
- pointIterators = []
- for data in self.data.values():
- pointNames.append(data.name)
- pointIterators.append(data)
-
- return self._iterator(pointNames, pointIterators)
-
- def _iterator(self, pointNames, pointIterators):
- for pointValues in itertools.product(*pointIterators):
- point = {}
- for i in range(len(pointValues)):
- point[pointNames[i]] = pointValues[i]
- yield point, self.function(point)
-
-
-class Data:
-
- def __init__(self, name, dataString, step=None):
- self.name = name
- self.step = step
- self.data = self.parseString(dataString)
-
- def parseString(self, dataString):
- if not isinstance(dataString, str):
- return Constant(dataString),
-
- dataList = []
- for data in dataString.split(";"):
- if isinstance(data, str) and "-" in data:
- # Dealing with a range
- dataList.append(Range(data, self.step))
- else:
- dataList.append(Constant(data))
-
- return dataList
-
- def __iter__(self):
- for data in self.data:
- for value in data:
- yield value
-
- def isConstant(self):
- return len(self.data) == 1 and self.data[0].isConstant()
-
-
-class Constant:
-
- def __init__(self, const):
- if isinstance(const, str):
- self.value = None if const == "" else float(const)
+ def _xIter(self, xRange, xAmount):
+ rangeStart, rangeEnd = sorted(xRange)
+ # Amount is amount of ranges between points here, not amount of points
+ step = (rangeEnd - rangeStart) / xAmount
+ if step == 0:
+ yield xRange[0]
else:
- self.value = const
+ current = rangeStart
+ # Take extra half step to make sure end of range is always included
+ # despite any possible float errors
+ while current <= (rangeEnd + step / 2):
+ yield current
+ current += step
- def __iter__(self):
- yield self.value
-
- @staticmethod
- def isConstant():
- return True
-
-
-class Range:
-
- def __init__(self, string, step):
- start, end = string.split("-")
- self.start = float(start)
- self.end = float(end)
- self.step = step
-
- def __iter__(self):
- current = start = self.start
- end = self.end
- step = self.step or (end - start) / 200
- i = 0
- while current < end:
- current = start + i * step
- i += 1
- yield current
-
- @staticmethod
- def isConstant():
- return False
+ def clearCache(self, fitID):
+ self.cache.clear()
diff --git a/eos/graph/fitCapAmountTime.py b/eos/graph/fitCapAmountTime.py
index c8dcf91d9..d8542c81d 100644
--- a/eos/graph/fitCapAmountTime.py
+++ b/eos/graph/fitCapAmountTime.py
@@ -1,24 +1,27 @@
import math
-from logbook import Logger
from eos.graph import Graph
-pyfalog = Logger(__name__)
-
-
class FitCapAmountTimeGraph(Graph):
- defaults = {"time": 0}
+ def getPlotPoints(self, fit, extraData, xRange, xAmount):
+ xs = []
+ ys = []
+ for x in self._xIter(xRange, xAmount):
+ xs.append(x)
+ ys.append(self.calcCap(fit, x))
+ return xs, {'capAmount': ys}
- def __init__(self, fit, data=None):
- Graph.__init__(self, fit, self.calcAmount, data if data is not None else self.defaults)
- self.fit = fit
+ def getYForX(self, fit, extraData, x):
+ return {'capAmount': self.calcCap(fit, x)}
- def calcAmount(self, data):
- time = data["time"]
- maxCap = self.fit.ship.getModifiedItemAttr('capacitorCapacity')
- regenTime = self.fit.ship.getModifiedItemAttr('rechargeRate') / 1000
+ @staticmethod
+ def calcCap(fit, time):
+ if time < 0:
+ return 0
+ maxCap = fit.ship.getModifiedItemAttr('capacitorCapacity')
+ regenTime = fit.ship.getModifiedItemAttr('rechargeRate') / 1000
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
cap = maxCap * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
return cap
diff --git a/gui/builtinGraphs/__init__.py b/gui/builtinGraphs/__init__.py
index ee4e01011..4b81c5914 100644
--- a/gui/builtinGraphs/__init__.py
+++ b/gui/builtinGraphs/__init__.py
@@ -1,13 +1,13 @@
# noinspection PyUnresolvedReferences
from gui.builtinGraphs import ( # noqa: E402,F401
- fitDpsRange,
- fitDpsTime,
- fitDmgTime,
- fitShieldRegenAmount,
- fitShieldAmountTime,
- fitCapRegenAmount,
+ # fitDpsRange,
+ # fitDpsTime,
+ # fitDmgTime,
+ # fitShieldRegenAmount,
+ # fitShieldAmountTime,
+ # fitCapRegenAmount,
fitCapAmountTime,
- fitSpeedTime,
- fitDistanceTime,
- fitWarpTimeDistance
+ # fitSpeedTime,
+ # fitDistanceTime,
+ # fitWarpTimeDistance
)
diff --git a/gui/builtinGraphs/fitCapAmountTime.py b/gui/builtinGraphs/fitCapAmountTime.py
index 60a0e9d48..42ce9f945 100644
--- a/gui/builtinGraphs/fitCapAmountTime.py
+++ b/gui/builtinGraphs/fitCapAmountTime.py
@@ -17,65 +17,31 @@
# along with pyfa. If not, see .
# =============================================================================
+
import gui.mainFrame
-from eos.graph import Data
-from eos.graph.fitCapAmountTime import FitCapAmountTimeGraph as EosFitCapAmountTimeGraph
-from gui.bitmap_loader import BitmapLoader
-from gui.graph import Graph
-from service.attribute import Attribute
+from eos.graph.fitCapAmountTime import FitCapAmountTimeGraph as EosGraph
+from gui.graph import Graph, XDef, YDef
class FitCapAmountTimeGraph(Graph):
- propertyLabelMap = {"time": "Time (seconds)"}
-
- defaults = EosFitCapAmountTimeGraph.defaults.copy()
+ name = 'Cap Amount vs Time'
def __init__(self):
- Graph.__init__(self)
- self.defaults["time"] = "0-300"
- self.name = "Cap Amount vs Time"
- self.eosGraph = None
+ self.eosGraph = EosGraph()
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
- def getFields(self):
- return self.defaults
+ @property
+ def xDef(self):
+ return XDef(handle='time', inputDefault='0-300', inputLabel='Time (seconds)', inputIconID=1392, axisLabel='Time, s')
- def getLabels(self):
- return self.propertyLabelMap
+ @property
+ def yDefs(self):
+ return [YDef(handle='capAmount', switchLabel='Cap amount', axisLabel='Cap amount, GJ')]
- def getIcons(self):
- iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
- bitmap = BitmapLoader.getBitmap(iconFile, "icons")
- return {"time": bitmap}
-
- def getPoints(self, fit, fields):
- eosGraph = getattr(self, "eosGraph", None)
- if eosGraph is None or eosGraph.fit != fit:
- eosGraph = self.eosGraph = EosFitCapAmountTimeGraph(fit)
-
- eosGraph.clearData()
- variable = None
- for fieldName, value in fields.items():
- d = Data(fieldName, value)
- if not d.isConstant():
- if variable is None:
- variable = fieldName
- else:
- # We can't handle more then one variable atm, OOPS FUCK OUT
- return False, "Can only handle 1 variable"
-
- eosGraph.setData(d)
-
- if variable is None:
- return False, "No variable"
-
- x = []
- y = []
- for point, val in eosGraph.getIterator():
- x.append(point[variable])
- y.append(val)
- return x, y
+ def getPlotPoints(self, fit, extraData, xRange, xAmount):
+ xRange = self.parseRange(xRange)
+ return self.eosGraph.getPlotPoints(fit, extraData, xRange, xAmount)
FitCapAmountTimeGraph.register()
diff --git a/gui/graph.py b/gui/graph.py
index f54e79767..3d02915b8 100644
--- a/gui/graph.py
+++ b/gui/graph.py
@@ -18,26 +18,59 @@
# =============================================================================
-class Graph(object):
+import re
+from abc import ABCMeta, abstractmethod
+from collections import namedtuple
+
+
+class Graph(metaclass=ABCMeta):
+
views = []
+ yTypes = None
@classmethod
def register(cls):
Graph.views.append(cls)
- def __init__(self):
- self.name = ""
+ @property
+ @abstractmethod
+ def name(self):
+ raise NotImplementedError
- def getFields(self, fit, fields):
- raise NotImplementedError()
+ @property
+ @abstractmethod
+ def xDef(self):
+ raise NotImplementedError
- def getIcons(self):
- return None
+ @property
+ def extraInputs(self):
+ return ()
+
+ @property
+ @abstractmethod
+ def yDefs(self):
+ raise NotImplementedError
@property
def redrawOnEffectiveChange(self):
return False
+ def parseRange(self, string):
+ m = re.match('\s*(?P\d+(\.\d+)?)\s*(-\s*(?P\d+(\.\d+)?))?', string)
+ if m is None:
+ return (0, 0)
+ first = m.group('first')
+ second = m.group('second')
+ if not second:
+ return (0, float(first))
+ else:
+ return (float(first), float(second))
+
+
+XDef = namedtuple('XDef', ('handle', 'inputDefault', 'inputLabel', 'inputIconID', 'axisLabel'))
+YDef = namedtuple('YDef', ('handle', 'switchLabel', 'axisLabel'))
+ExtraInput = namedtuple('ExtraInput', ('handle', 'inputDefault', 'inputLabel', 'inputIconID'))
+
# noinspection PyUnresolvedReferences
from gui.builtinGraphs import *
diff --git a/gui/graphFrame.py b/gui/graphFrame.py
index fe8b79fbe..3ccb482cf 100644
--- a/gui/graphFrame.py
+++ b/gui/graphFrame.py
@@ -19,6 +19,7 @@
import os
import traceback
+from itertools import chain
# noinspection PyPackageRequirements
import wx
@@ -230,50 +231,42 @@ class GraphFrame(wx.Frame):
def getValues(self):
values = {}
- for fieldName, field in self.fields.items():
- values[fieldName] = field.GetValue()
+ for fieldHandle, field in self.fields.items():
+ values[fieldHandle] = field.GetValue()
return values
def select(self, index):
view = self.getView()
- icons = view.getIcons()
- labels = view.getLabels()
sizer = self.gridSizer
sizer.Clear()
self.gridPanel.DestroyChildren()
self.fields.clear()
# Setup textboxes
- for field, defaultVal in view.getFields().items():
-
+ for fieldDef in (view.xDef, *view.extraInputs):
textBox = wx.TextCtrl(self.gridPanel, wx.ID_ANY, style=0)
- self.fields[field] = textBox
+ self.fields[fieldDef.handle] = textBox
textBox.Bind(wx.EVT_TEXT, self.onFieldChanged)
sizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3)
- if defaultVal is not None:
- if not isinstance(defaultVal, str):
- defaultVal = ("%f" % defaultVal).rstrip("0")
- if defaultVal[-1:] == ".":
- defaultVal += "0"
+ if fieldDef.inputDefault is not None:
+ inputDefault = fieldDef.inputDefault
+ if not isinstance(inputDefault, str):
+ inputDefault = ("%f" % inputDefault).rstrip("0")
+ if inputDefault[-1:] == ".":
+ inputDefault += "0"
- textBox.ChangeValue(defaultVal)
+ textBox.ChangeValue(inputDefault)
imgLabelSizer = wx.BoxSizer(wx.HORIZONTAL)
- if icons:
- icon = icons.get(field)
+ if fieldDef.inputIconID:
+ icon = BitmapLoader.getBitmap(fieldDef.inputIconID, "icons")
if icon is not None:
static = wx.StaticBitmap(self.gridPanel)
static.SetBitmap(icon)
imgLabelSizer.Add(static, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 1)
- if labels:
- label = labels.get(field)
- label = label if label is not None else field
- else:
- label = field
-
- imgLabelSizer.Add(wx.StaticText(self.gridPanel, wx.ID_ANY, label), 0,
+ imgLabelSizer.Add(wx.StaticText(self.gridPanel, wx.ID_ANY, fieldDef.inputLabel), 0,
wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
sizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL)
sizer.Layout()
@@ -299,19 +292,20 @@ class GraphFrame(wx.Frame):
min_y = 0
max_y = 0
+
+ xRange = values[view.xDef.handle]
+ extraInputs = {i.handle: values[i.handle] for i in view.extraInputs}
+ chosenY = view.yDefs[0].handle
+
for fit in self.fits:
try:
- success, status = view.getPoints(fit, values)
- if not success:
- # TODO: Add a pwetty statys bar to report errors with
- self.SetStatusText(status)
- return
+ xs, ys = view.getPlotPoints(fit, extraInputs, xRange, 100)
+ ys = ys[chosenY]
- x, y = success, status
- min_y = min(min_y, min(y, default=0))
- max_y = max(max_y, max(y, default=0))
+ min_y = min(min_y, min(ys, default=0))
+ max_y = max(max_y, max(ys, default=0))
- self.subplot.plot(x, y)
+ self.subplot.plot(xs, ys)
legend.append('{} ({})'.format(fit.name, fit.ship.item.getShortName()))
except Exception as ex:
pyfalog.warning("Invalid values in '{0}'", fit.name)