def dpsToolTip(preSpool, fullSpool, prec, lowest, highest): if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec): return "" else: return "Spool up: {}-{}".format( formatAmount(preSpool, prec, lowest, highest), formatAmount(fullSpool, prec, lowest, highest))
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest): if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec): return False, text else: return True, "{}\nSpool up: {}-{}".format( text, formatAmount(preSpool, prec, lowest, highest), formatAmount(fullSpool, prec, lowest, highest))
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest): if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec): return "" else: return "Spool up: {}-{}".format( formatAmount(preSpool, prec, lowest, highest), formatAmount(fullSpool, prec, lowest, highest))
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest): if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec): return False, text else: return True, "{}\nSpool up: {}-{}".format( text, formatAmount(preSpool, prec, lowest, highest), formatAmount(fullSpool, prec, lowest, highest))
def getYForX(self, fit, extraData, x): time = x * 1000 cache = self._cache[fit.ID] closestTime = max((t for t in cache if t <= time), default=None) if closestTime is None: return 0 return roundToPrec(cache[closestTime], 6)
def getYForX(self, fit, extraData, x): time = x * 1000 cache = self._cache[fit.ID] closestTime = max((t for t in cache if t <= time), default=None) if closestTime is None: return 0 return roundToPrec(cache[closestTime], 6)
def addYMark(val): if val is None: return rounded = roundToPrec(val, 4) # If due to some bug or insufficient plot density we're # out of bounds, do not add anything if minY <= val <= maxY or minY <= rounded <= maxY: yMarks.add(rounded)
def addYMark(val): if val is None: return # If due to some bug or insufficient plot density we're # out of bounds, do not add anything if minY <= val <= maxY: if abs(val) < 0.0001: val = 0 else: val = roundToPrec(val, 4) yMarks.add(val)
def addYMark(val): if val is None: return # Round according to shown Y range - the bigger the range, # the rougher the rounding if yDiff != 0: rounded = roundToPrec(val, 4, nsValue=yDiff) else: rounded = val # If due to some bug or insufficient plot density we're # out of bounds, do not add anything if minY <= val <= maxY or minY <= rounded <= maxY: yMarks.add(rounded)
def renderMutant(mutant, firstPrefix='', prefix=''): exportLines = [] mutatedAttrs = {} for attrID, mutator in mutant.mutators.items(): attrName = getAttributeInfo(attrID).name mutatedAttrs[attrName] = mutator.value exportLines.append('{}{}'.format(firstPrefix, mutant.baseItem.name)) exportLines.append('{}{}'.format(prefix, mutant.mutaplasmid.item.name)) # Round to 7th significant number to avoid exporting float errors customAttrsLine = ', '.join( '{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7)) for a in sorted(mutatedAttrs)) exportLines.append('{}{}'.format(prefix, customAttrsLine)) return '\n'.join(exportLines)
def renderMutant(mutant, firstPrefix='', prefix=''): exportLines = [] mutatedAttrs = {} for attrID, mutator in mutant.mutators.items(): attrName = getAttributeInfo(attrID).name mutatedAttrs[attrName] = mutator.value exportLines.append('{}{}'.format(firstPrefix, mutant.baseItem.name)) exportLines.append('{}{}'.format(prefix, mutant.mutaplasmid.item.name)) # Round to 7th significant number to avoid exporting float errors customAttrsLine = ', '.join( '{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7)) for a in sorted(mutatedAttrs)) exportLines.append('{}{}'.format(prefix, customAttrsLine)) return '\n'.join(exportLines)
def getPlotPoints(self, fit, extraData, xRange, xAmount): # We deliberately ignore xAmount here to build graph which will reflect # all steps of building up the damage minX, maxX = self._limitXRange(xRange, fit, extraData) if fit.ID not in self._cache: self.__generateCache(fit, maxX) currentY = None prevY = None xs = [] ys = [] cache = self._cache[fit.ID] for time in sorted(cache): prevY = currentY currentX = time / 1000 currentY = roundToPrec(cache[time], 6) if currentX < minX: continue # First set of data points if not xs: # Start at exactly requested time, at last known value initialY = prevY or 0 xs.append(minX) ys.append(initialY) # If current time is bigger then starting, extend plot to that time with old value if currentX > minX: xs.append(currentX) ys.append(initialY) # If new value is different, extend it with new point to the new value if currentY != prevY: xs.append(currentX) ys.append(currentY) continue # Last data point if currentX >= maxX: xs.append(maxX) ys.append(prevY) break # Anything in-between if currentY != prevY: if prevY is not None: xs.append(currentX) ys.append(prevY) xs.append(currentX) ys.append(currentY) if max(xs) < maxX: xs.append(maxX) ys.append(currentY or 0) return xs, ys
def getToolTip(self, mod): lines = [] missileRangeData = mod.missileMaxRangeData if hasattr( mod, "missileMaxRangeData") else None if missileRangeData is not None: lines.append('Missile flight range') lowerRange, higherRange, higherChance = missileRangeData if roundToPrec(higherChance, 3) not in (0, 1): lines.append('{}% chance to fly {}m'.format( formatAmount((1 - higherChance) * 100, prec=3, lowest=0, highest=0), formatAmount(lowerRange, prec=3, lowest=0, highest=3))) lines.append('{}% chance to fly {}m'.format( formatAmount(higherChance * 100, prec=3, lowest=0, highest=0), formatAmount(higherRange, prec=3, lowest=0, highest=3))) else: lines.append("Optimal + Falloff") return '\n'.join(lines)
def hasSpoolUp(preSpool, fullSpool): if preSpool is None or fullSpool is None: return False return roundToPrec(preSpool.total, prec) != roundToPrec( fullSpool.total, prec)
def draw(self, accurateMarks=True): self.subplot.clear() self.subplot.grid(True) allXs = set() allYs = set() plotData = {} legendData = [] chosenX = self.graphFrame.ctrlPanel.xType chosenY = self.graphFrame.ctrlPanel.yType self.subplot.set(xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX), ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY)) mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues() view = self.graphFrame.getView() sources = self.graphFrame.ctrlPanel.sources if view.hasTargets: iterList = tuple( itertools.product(sources, self.graphFrame.ctrlPanel.targets)) else: iterList = tuple((f, None) for f in sources) # Draw plot lines and get data for legend for source, target in iterList: # Get line style data try: colorData = BASE_COLORS[source.colorID] except KeyError: pyfalog.warning('Invalid color "{}" for "{}"'.format( source.colorID, source.name)) continue color = colorData.hsl lineStyle = 'solid' if target is not None: try: lightnessData = LIGHTNESSES[target.lightnessID] except KeyError: pyfalog.warning('Invalid lightness "{}" for "{}"'.format( target.lightnessID, target.name)) continue color = lightnessData.func(color) try: lineStyleData = STYLES[target.lineStyleID] except KeyError: pyfalog.warning('Invalid line style "{}" for "{}"'.format( target.lightnessID, target.name)) continue lineStyle = lineStyleData.mplSpec color = hsv_to_rgb(hsl_to_hsv(color)) # Get point data try: xs, ys = view.getPlotPoints(mainInput=mainInput, miscInputs=miscInputs, xSpec=chosenX, ySpec=chosenY, src=source, tgt=target) if not self.__checkNumbers(xs, ys): pyfalog.warning( 'Failed to plot "{}" vs "{}" due to inf or NaN in values' .format(source.name, '' if target is None else target.name)) continue 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 if len(xs) == 1 and len(ys) == 1: self.subplot.plot(xs, ys, color=color, linestyle=lineStyle, marker='.') else: self.subplot.plot(xs, ys, color=color, linestyle=lineStyle) # Fill data for legend if target is None: legendData.append((color, lineStyle, source.shortName)) else: legendData.append( (color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName))) except Exception: pyfalog.warning('Failed to plot "{}" vs "{}"'.format( source.name, '' if target is None else target.name)) self.canvas.draw() self.Refresh() return # Setting Y limits for canvas if self.graphFrame.ctrlPanel.showY0: allYs.add(0) canvasMinY, canvasMaxY = self._getLimits(allYs, minExtra=0.05, maxExtra=0.1) canvasMinX, canvasMaxX = self._getLimits(allXs, minExtra=0.02, maxExtra=0.02) self.subplot.set_ylim(bottom=canvasMinY, top=canvasMaxY) self.subplot.set_xlim(left=canvasMinX, right=canvasMaxX) # Process X marks line if self.xMark is not None: minX = min(allXs, default=None) maxX = max(allXs, default=None) if minX is not None and maxX is not None: minY = min(allYs, default=None) maxY = max(allYs, default=None) xMark = max(min(self.xMark, maxX), minX) # If in top 10% of X coordinates, align labels differently if xMark > canvasMinX + 0.9 * (canvasMaxX - canvasMinX): labelAlignment = 'right' labelPrefix = '' labelSuffix = ' ' else: labelAlignment = 'left' labelPrefix = ' ' labelSuffix = '' # 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(labelPrefix, roundToPrec(xMark, 4), labelSuffix) else: xLabel = '{}{} {}{}'.format(labelPrefix, roundToPrec(xMark, 4), chosenX.unit, labelSuffix) self.subplot.annotate(xLabel, xy=(xMark, canvasMaxY - 0.01 * (canvasMaxY - canvasMinY)), xytext=(0, 0), annotation_clip=False, textcoords='offset pixels', ha=labelAlignment, va='top', fontsize='small') # Get Y values yMarks = set() def addYMark(val): if val is None: return # If due to some bug or insufficient plot density we're # out of bounds, do not add anything if minY <= val <= maxY: if abs(val) < 0.0001: val = 0 else: val = roundToPrec(val, 4) yMarks.add(val) 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) yMark = self._interpolateX(x=xMark, x1=xs[idx - 1], y1=ys[idx - 1], x2=xs[idx], y2=ys[idx]) addYMark(yMark) # Draw Y values for yMark in yMarks: self.subplot.annotate('{}{}{}'.format( labelPrefix, yMark, labelSuffix), xy=(xMark, yMark), xytext=(0, 0), textcoords='offset pixels', ha=labelAlignment, va='center', fontsize='small') legendLines = [] for i, iData in enumerate(legendData): color, lineStyle, label = iData legendLines.append( Line2D([0], [0], color=color, linestyle=lineStyle, label=label.replace('$', '\$'))) if len(legendLines) > 0 and self.graphFrame.ctrlPanel.showLegend: legend = self.subplot.legend(handles=legendLines) for t in legend.get_texts(): t.set_fontsize('small') for l in legend.get_lines(): l.set_linewidth(1) self.canvas.draw() self.Refresh()
def exportEft(fit, options): # EFT formatted export is split in several sections, each section is # separated from another using 2 blank lines. Sections might have several # sub-sections, which are separated by 1 blank line sections = [] header = '[{}, {}]'.format(fit.ship.item.name, fit.name) # Section 1: modules, rigs, subsystems, services modsBySlotType = {} sFit = svcFit.getInstance() for module in fit.modules: modsBySlotType.setdefault(module.slot, []).append(module) modSection = [] mutants = {} # Format: {reference number: module} mutantReference = 1 for slotType in SLOT_ORDER: rackLines = [] modules = modsBySlotType.get(slotType, ()) for module in modules: if module.item: mutated = bool(module.mutators) # if module was mutated, use base item name for export if mutated: modName = module.baseItem.name else: modName = module.item.name if mutated and options & Options.MUTATIONS.value: mutants[mutantReference] = module mutationSuffix = ' [{}]'.format(mutantReference) mutantReference += 1 else: mutationSuffix = '' modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == State.OFFLINE else '' if module.charge and sFit.serviceFittingOptions['exportCharges']: rackLines.append('{}, {}{}{}'.format( modName, module.charge.name, modOfflineSuffix, mutationSuffix)) else: rackLines.append('{}{}{}'.format(modName, modOfflineSuffix, mutationSuffix)) else: rackLines.append('[Empty {} slot]'.format( Slot.getName(slotType).capitalize() if slotType is not None else '')) if rackLines: modSection.append('\n'.join(rackLines)) if modSection: sections.append('\n\n'.join(modSection)) # Section 2: drones, fighters minionSection = [] droneLines = [] for drone in sorted(fit.drones, key=lambda d: d.item.name): droneLines.append('{} x{}'.format(drone.item.name, drone.amount)) if droneLines: minionSection.append('\n'.join(droneLines)) fighterLines = [] for fighter in sorted(fit.fighters, key=lambda f: f.item.name): fighterLines.append('{} x{}'.format(fighter.item.name, fighter.amountActive)) if fighterLines: minionSection.append('\n'.join(fighterLines)) if minionSection: sections.append('\n\n'.join(minionSection)) # Section 3: implants, boosters if options & Options.IMPLANTS.value: charSection = [] implantLines = [] for implant in fit.implants: implantLines.append(implant.item.name) if implantLines: charSection.append('\n'.join(implantLines)) boosterLines = [] for booster in fit.boosters: boosterLines.append(booster.item.name) if boosterLines: charSection.append('\n'.join(boosterLines)) if charSection: sections.append('\n\n'.join(charSection)) # Section 4: cargo cargoLines = [] for cargo in sorted( fit.cargo, key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name) ): cargoLines.append('{} x{}'.format(cargo.item.name, cargo.amount)) if cargoLines: sections.append('\n'.join(cargoLines)) # Section 5: mutated modules' details mutationLines = [] if mutants and options & Options.MUTATIONS.value: for mutantReference in sorted(mutants): mutant = mutants[mutantReference] mutatedAttrs = {} for attrID, mutator in mutant.mutators.items(): attrName = getAttributeInfo(attrID).name mutatedAttrs[attrName] = mutator.value mutationLines.append('[{}] {}'.format(mutantReference, mutant.baseItem.name)) mutationLines.append(' {}'.format(mutant.mutaplasmid.item.name)) # Round to 7th significant number to avoid exporting float errors customAttrsLine = ', '.join( '{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7)) for a in sorted(mutatedAttrs)) mutationLines.append(' {}'.format(customAttrsLine)) if mutationLines: sections.append('\n'.join(mutationLines)) return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))