Added vertical marker line to show exact values on graphs

This commit is contained in:
DarkPhoenix
2019-08-15 17:25:25 +03:00
parent d736a10dc9
commit be07a4735c
2 changed files with 125 additions and 69 deletions

View File

@@ -31,62 +31,61 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
return currentUnwebbedSpeed
maxUnwebbedSpeed = tgt.getMaxVelocity()
try:
speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
except ZeroDivisionError:
currentWebbedSpeed = 0
else:
appliedMultipliers = {}
# Modules first, they are applied always the same way
for wData in webMods:
appliedBoost = wData.boost * calculateRangeFactor(
srcOptimalRange=wData.optimal,
srcFalloffRange=wData.falloff,
distance=distance)
if appliedBoost:
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
# What's immobile cannot be slowed
if maxUnwebbedSpeed == 0:
return maxUnwebbedSpeed
speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
appliedMultipliers = {}
# Modules first, they are applied always the same way
for wData in webMods:
appliedBoost = wData.boost * calculateRangeFactor(
srcOptimalRange=wData.optimal,
srcFalloffRange=wData.falloff,
distance=distance)
if appliedBoost:
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Drones and fighters
mobileWebs = []
mobileWebs.extend(webFighters)
# Drones have range limit
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
mobileWebs.extend(webDrones)
atkRadius = src.getRadius()
# As mobile webs either follow the target or stick to the attacking ship,
# if target is within mobile web optimal - it can be applied unconditionally
longEnoughMws = [mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius]
if longEnoughMws:
for mwData in longEnoughMws:
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Drones and fighters
mobileWebs = []
mobileWebs.extend(webFighters)
# Drones have range limit
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
mobileWebs.extend(webDrones)
atkRadius = src.getRadius()
# As mobile webs either follow the target or stick to the attacking ship,
# if target is within mobile web optimal - it can be applied unconditionally
longEnoughMws = [mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius]
if longEnoughMws:
for mwData in longEnoughMws:
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Apply remaining webs, from fastest to slowest
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
while mobileWebs:
# Process in batches unified by speed to save up resources
fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed
fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
for mwData in fastestMws:
# Faster than target or set to follow it - apply full slowdown
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
appliedMwBoost = mwData.boost
# Otherwise project from the center of the ship
# Apply remaining webs, from fastest to slowest
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
while mobileWebs:
# Process in batches unified by speed to save up resources
fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed
fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
for mwData in fastestMws:
# Faster than target or set to follow it - apply full slowdown
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
appliedMwBoost = mwData.boost
# Otherwise project from the center of the ship
else:
if distance is None:
rangeFactorDistance = None
else:
if distance is None:
rangeFactorDistance = None
else:
rangeFactorDistance = distance + atkRadius - mwData.radius
appliedMwBoost = mwData.boost * calculateRangeFactor(
srcOptimalRange=mwData.optimal,
srcFalloffRange=mwData.falloff,
distance=rangeFactorDistance)
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
rangeFactorDistance = distance + atkRadius - mwData.radius
appliedMwBoost = mwData.boost * calculateRangeFactor(
srcOptimalRange=mwData.optimal,
srcFalloffRange=mwData.falloff,
distance=rangeFactorDistance)
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Ensure consistent results - round off a little to avoid float errors
return floatUnerr(currentWebbedSpeed)

View File

@@ -21,6 +21,7 @@
import itertools
import os
import traceback
from bisect import bisect
# noinspection PyPackageRequirements
@@ -29,6 +30,7 @@ from logbook import Logger
from graphs.style import BASE_COLORS, LIGHTNESSES, STYLES, hsl_to_hsv
from gui.utils.numberFormatter import roundToPrec
pyfalog = Logger(__name__)
@@ -99,6 +101,7 @@ class GraphCanvasPanel(wx.Panel):
self.subplot.grid(True)
allXs = set()
allYs = set()
plotData = {}
legendData = []
chosenX = self.graphFrame.ctrlPanel.xType
chosenY = self.graphFrame.ctrlPanel.yType
@@ -146,6 +149,7 @@ class GraphCanvasPanel(wx.Panel):
ySpec=chosenY,
src=source,
tgt=target)
plotData[(source, target)] = (xs, ys)
allXs.update(xs)
allYs.update(ys)
# If we have single data point, show marker - otherwise line won't be shown
@@ -159,7 +163,7 @@ class GraphCanvasPanel(wx.Panel):
else:
legendData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName)))
except Exception:
pyfalog.warning('Invalid values in "{0}"', source.name)
pyfalog.warning('Failed to plot "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
self.canvas.draw()
self.Refresh()
return
@@ -171,17 +175,17 @@ class GraphCanvasPanel(wx.Panel):
maxY = max(allYs, default=0)
# Extend range a little for some visual space
yRange = maxY - minY
minY -= yRange * 0.05
maxY += yRange * 0.05
canvasMinY = minY - yRange * 0.05
canvasMaxY = maxY + yRange * 0.1 # Extra space for "X mark"
# Extend by % of value if we show function of a constant
if minY == maxY:
minY -= minY * 0.05
maxY += minY * 0.05
if canvasMinY == canvasMaxY:
canvasMinY -= canvasMinY * 0.05
canvasMaxY += canvasMinY * 0.05
# If still equal, function is 0, spread out visual space as special case
if minY == maxY:
minY -= 5
maxY += 5
self.subplot.set_ylim(bottom=minY, top=maxY)
if canvasMinY == canvasMaxY:
canvasMinY -= 5
canvasMaxY += 5
self.subplot.set_ylim(bottom=canvasMinY, top=canvasMaxY)
# Process X marks line
if self.xMark is not None:
@@ -189,8 +193,62 @@ class GraphCanvasPanel(wx.Panel):
maxX = max(allXs, default=None)
if minX is not None and maxX is not None:
xMark = max(min(self.xMark, maxX), minX)
# Draw line
self.subplot.axvline(x=xMark, linestyle='dotted', linewidth=1, color=(0, 0, 0))
# Draw its X position
if chosenX.unit is None:
xLabel = ' {}'.format(roundToPrec(xMark, 4))
else:
xLabel = ' {} {}'.format(roundToPrec(xMark, 4), chosenX.unit)
self.subplot.annotate(
xLabel, xy=(xMark, maxY + 0.66 * (canvasMaxY - maxY)), xytext=(-1, -1),
textcoords='offset pixels', ha='left', va='center', fontsize='small')
# Get Y values
yMarks = set()
def addYMark(val):
yMarks.add(roundToPrec(val, 4))
for source, target in iterList:
xs, ys = plotData[(source, target)]
if not xs or xMark < min(xs) or xMark > max(xs):
continue
# Fetch values from graphs when we're asked to provide accurate data
if accurateMarks:
try:
y = view.getPoint(
x=xMark,
miscInputs=miscInputs,
xSpec=chosenX,
ySpec=chosenY,
src=source,
tgt=target)
addYMark(y)
except Exception:
pyfalog.warning('Failed to get X mark for "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
# Silently skip this mark, otherwise other marks and legend display will fail
continue
# Otherwise just do linear interpolation between two points
else:
if xMark in xs:
# We might have multiples of the same value in our sequence, pick value for the last one
idx = len(xs) - xs[::-1].index(xMark) - 1
addYMark(ys[idx])
continue
idx = bisect(xs, xMark)
xLeft = xs[idx - 1]
xRight = xs[idx]
yLeft = ys[idx - 1]
yRight = ys[idx]
pos = (xMark - xLeft) / (xRight - xLeft)
yMark = yLeft + pos * (yRight - yLeft)
addYMark(yMark)
# Draw Y values
for yMark in yMarks:
self.subplot.annotate(
' {}'.format(yMark), xy=(xMark, yMark), xytext=(-1, -1),
textcoords='offset pixels', ha='left', va='center', fontsize='small')
legendLines = []
for i, iData in enumerate(legendData):
@@ -212,11 +270,6 @@ class GraphCanvasPanel(wx.Panel):
self.xMark = x
self.draw(accurateMarks=False)
def markXAccurate(self, x):
if x is not None:
self.xMark = x
self.draw()
def unmarkX(self):
self.xMark = None
self.draw()
@@ -243,4 +296,8 @@ class GraphCanvasPanel(wx.Panel):
if self.mplOnReleaseHandler:
self.canvas.mpl_disconnect(self.mplOnReleaseHandler)
self.mplOnReleaseHandler = None
self.markXAccurate(event.xdata)
# Do not write markX here because of strange mouse behavior: when dragging,
# sometimes when you release button, x coordinate changes. To avoid that,
# we just re-use coordinates set on click/drag and just request to redraw
# using accurate data
self.draw(accurateMarks=True)