Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions graphs/gui/canvasPanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,59 @@
from logbook import Logger


from eos.saveddata.fit import Fit
from eos.saveddata.targetProfile import TargetProfile
from graphs.style import BASE_COLORS, LIGHTNESSES, STYLES, hsl_to_hsv
from gui.utils.numberFormatter import roundToPrec


pyfalog = Logger(__name__)


def _graph_item_key(item):
if isinstance(item, Fit):
return ('fit', item.ID)
if isinstance(item, TargetProfile):
return ('profile', item.ID)
return None


def expand_reverse_filtered_matchups(ctrl, base_pairs):
"""
For each (attacker, target) in base_pairs, optionally add (targetShip as attacker, attackerShip as target)
using the SourceWrapper / TargetWrapper instances from the full lists when present.
"""
if not ctrl.showReverseFilteredMatchups:
return base_pairs
src_by_key = {}
for w in ctrl.sources:
k = _graph_item_key(w.item)
if k:
src_by_key[k] = w
tgt_by_key = {}
for w in ctrl.targets:
k = _graph_item_key(w.item)
if k:
tgt_by_key[k] = w
seen = set((id(s), id(t)) for s, t in base_pairs)
out = list(base_pairs)
for s, t in base_pairs:
ks = _graph_item_key(t.item)
kt = _graph_item_key(s.item)
if ks is None or kt is None:
continue
rev_s = src_by_key.get(ks)
rev_t = tgt_by_key.get(kt)
if rev_s is None or rev_t is None:
continue
key = (id(rev_s), id(rev_t))
if key in seen:
continue
seen.add(key)
out.append((rev_s, rev_t))
return out


try:
import matplotlib as mpl

Expand Down Expand Up @@ -116,9 +162,11 @@ def draw(self, accurateMarks=True):

mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues()
view = self.graphFrame.getView()
sources = self.graphFrame.ctrlPanel.sources
ctrl = self.graphFrame.ctrlPanel
sources = ctrl.filteredSources
if view.hasTargets:
iterList = tuple(itertools.product(sources, self.graphFrame.ctrlPanel.targets))
base_pairs = list(itertools.product(sources, ctrl.filteredTargets))
iterList = tuple(expand_reverse_filtered_matchups(ctrl, base_pairs))
else:
iterList = tuple((f, None) for f in sources)

Expand Down
40 changes: 40 additions & 0 deletions graphs/gui/ctrlPanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ def __init__(self, graphFrame, parent):
self.showY0Cb.SetValue(True)
self.showY0Cb.Bind(wx.EVT_CHECKBOX, self.OnShowY0Change)
commonOptsSizer.Add(self.showY0Cb, 0, wx.EXPAND | wx.TOP, 5)
self.reverseFilteredMatchupsCb = wx.CheckBox(
self, wx.ID_ANY, _t('Include reverse matchups when filtered'), wx.DefaultPosition, wx.DefaultSize, 0)
self.reverseFilteredMatchupsCb.SetValue(False)
self.reverseFilteredMatchupsCb.Bind(wx.EVT_CHECKBOX, self.OnReverseFilteredMatchupsChange)
self.reverseFilteredMatchupsCb.SetToolTip(wx.ToolTip(_t(
'Also plot target→attacker for the same ships. Add each ship to both attacker and target lists.')))
commonOptsSizer.Add(self.reverseFilteredMatchupsCb, 0, wx.EXPAND | wx.TOP, 5)
optsSizer.Add(commonOptsSizer, 0, wx.EXPAND | wx.RIGHT, 10)

graphOptsSizer = wx.BoxSizer(wx.HORIZONTAL)
Expand Down Expand Up @@ -158,6 +165,7 @@ def updateControls(self, layout=True):
# Source and target list
self.refreshColumns(layout=False)
self.targetList.Show(view.hasTargets)
self.reverseFilteredMatchupsCb.Show(view.hasTargets)

# Inputs
self._updateInputs(storeInputs=False)
Expand Down Expand Up @@ -327,6 +335,10 @@ def OnShowY0Change(self, event):
event.Skip()
self.graphFrame.draw()

def OnReverseFilteredMatchupsChange(self, event):
event.Skip()
self.graphFrame.draw()

def OnYTypeUpdate(self, event):
event.Skip()
self._updateInputs()
Expand Down Expand Up @@ -417,6 +429,34 @@ def sources(self):
def targets(self):
return self.targetList.wrappers

@property
def filteredSources(self):
srcs = self.sources
selected = self.sourceList.getSelectedWrappers()
if not selected:
return srcs
sel = set(selected)
return [w for w in srcs if w in sel]

@property
def filteredTargets(self):
tgts = self.targets
selected = self.targetList.getSelectedWrappers()
if not selected:
return tgts
sel = set(selected)
return [w for w in tgts if w in sel]

@property
def isGraphFiltered(self):
return bool(self.sourceList.getSelectedWrappers()) or bool(self.targetList.getSelectedWrappers())

@property
def showReverseFilteredMatchups(self):
if not self.graphFrame.getView().hasTargets or not self.isGraphFiltered:
return False
return self.reverseFilteredMatchupsCb.GetValue()

# Fit events
def OnFitRenamed(self, event):
self.sourceList.OnFitRenamed(event)
Expand Down
52 changes: 33 additions & 19 deletions graphs/gui/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ def __init__(self, graphFrame, parent):

self.hoveredRow = None
self.hoveredColumn = None
self._graphSelectionRedrawPending = False

self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnGraphListSelectionChanged)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnGraphListSelectionChanged)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
Expand All @@ -56,6 +59,16 @@ def wrappers(self):
# Sort fits first, then target profiles
return sorted(self._wrappers, key=lambda w: not w.isFit)

def OnGraphListSelectionChanged(self, event):
event.Skip()
if not self._graphSelectionRedrawPending:
self._graphSelectionRedrawPending = True
wx.CallAfter(self._flushGraphSelectionRedraw)

def _flushGraphSelectionRedraw(self):
self._graphSelectionRedrawPending = False
self.graphFrame.draw()

# UI-related stuff
@property
def defaultTTText(self):
Expand Down Expand Up @@ -121,23 +134,24 @@ def handleDrag(self, type, fitID):

def OnLeftDown(self, event):
row, _ = self.HitTest(event.Position)
if row != -1:
pickers = {
self.getColIndex(GraphColor): ColorPickerPopup,
self.getColIndex(GraphLightness): LightnessPickerPopup,
self.getColIndex(GraphLineStyle): LineStylePickerPopup}
# In case we had no index for some column, remove None
pickers.pop(None, None)
col = self.getColumn(event.Position)
if col in pickers:
picker = pickers[col]
wrapper = self.getWrapper(row)
if wrapper is not None:
win = picker(parent=self, wrapper=wrapper)
pos = wx.GetMousePosition()
win.Position(pos, (0, 0))
win.Popup()
return
if row == -1:
self.unselectAll()
event.Skip()
return
pickers = {
self.getColIndex(GraphColor): ColorPickerPopup,
self.getColIndex(GraphLightness): LightnessPickerPopup,
self.getColIndex(GraphLineStyle): LineStylePickerPopup}
pickers.pop(None, None)
col = self.getColumn(event.Position)
if col in pickers:
wrapper = self.getWrapper(row)
if wrapper is not None:
win = pickers[col](parent=self, wrapper=wrapper)
pos = wx.GetMousePosition()
win.Position(pos, (0, 0))
win.Popup()
return
event.Skip()

def OnLineStyleChange(self):
Expand Down Expand Up @@ -310,7 +324,7 @@ def spawnMenu(self, event):

@property
def defaultTTText(self):
return _t('Drag a fit into this list to graph it')
return _t('Drag a fit into this list to graph it. Select rows to filter attackers (Shift/Ctrl); click empty space to show all attackers.')


class TargetWrapperList(BaseWrapperList):
Expand Down Expand Up @@ -367,7 +381,7 @@ def OnResistModeChanged(self, event):

@property
def defaultTTText(self):
return _t('Drag a fit into this list to have your fits graphed against it')
return _t('Drag a fit into this list to have your fits graphed against it. Select rows to filter targets (Shift/Ctrl); click empty space to show all targets.')

# Context menu handlers
def addProfile(self, profile):
Expand Down