package de.lighti.io; import java.awt.Color; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.DateTickMarkPosition; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYDifferenceRenderer; import org.jfree.data.time.FixedMillisecond; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.xy.XYSeries; import de.lighti.components.map.MapComponent; import de.lighti.components.match.GameStatisticsComponent; import de.lighti.model.AppState; import de.lighti.model.Statics; import de.lighti.model.game.Ability; import de.lighti.model.game.Dota2Item; import de.lighti.model.game.Hero; import de.lighti.model.game.Player; import de.lighti.model.game.Unit; import de.lighti.model.game.Unit.Zone; public final class ChartCreator { public static String[][] createAbilityLog( Player p ) { final List<Object[]> log = new ArrayList(); final Hero h = p.getHero(); for (final Ability a : h.getAbilities()) { for (final long l : a.getInvocations()) { final Object[] o = new Object[4]; o[0] = l; o[1] = h.getX( l ); o[2] = h.getY( l ); o[3] = a.getKey(); log.add( o ); } } Collections.sort( log, new Comparator<Object[]>() { @Override public int compare( Object[] o1, Object[] o2 ) { final long l1 = (long) o1[0]; final long l2 = (long) o2[0]; return Long.compare( l1, l2 ); } } ); final String[][] ret = new String[log.size()][4]; for (int i = 0; i < log.size(); i++) { final Object[] o = log.get( i ); ret[i][0] = o[0].toString(); ret[i][1] = o[1].toString(); ret[i][2] = o[2].toString(); ret[i][3] = o[3].toString(); } return ret; } public static XYSeries createAbilityMap( Hero hero, String name ) { final Ability ability = hero.getAbilityByName( name ); if (ability == null) { throw new IllegalArgumentException( "Hero " + hero.getName() + " has no ability " + name ); } final XYSeries ret = new XYSeries( name + MapComponent.CAT_ABILITIES, false, true ); for (final Long l : ability.getInvocations()) { ret.add( hero.getX( l ), hero.getY( l ) ); } return ret; } /** * This method creates a data set with two series(one radiant, one dire) representing the average * distance between all members of that team. For each timestep, the symmetrical half of * and Euclidian distance matrix is calculated, and the entries for players of the same team are * added to the sum. Each sum is then divided by 5. * @param appState the current app state containing player data * @return a dat set containing two data series */ private static TimeSeriesCollection createAverageTeamDistanceDataSet( AppState appState ) { final TimeSeriesCollection series = new TimeSeriesCollection(); final TimeSeries goodGuys = new TimeSeries( Statics.RADIANT ); final TimeSeries badGuys = new TimeSeries( Statics.DIRE ); final List<Hero> radiant = new ArrayList<Hero>(); final List<Hero> dire = new ArrayList<Hero>(); for (final Player p : appState.getPlayers()) { if (p.isRadiant()) { radiant.add( p.getHero() ); } else { dire.add( p.getHero() ); } } for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { long baddyDistance = 0l; long goodDistance = 0l; //Radiant outerLoop: for (final Hero h : radiant) { for (final Hero i : radiant) { if (h == i) { continue outerLoop; } final int xDiff = i.getX( seconds ) - h.getX( seconds ); final int yDiff = i.getY( seconds ) - h.getY( seconds ); goodDistance += Math.sqrt( Math.pow( xDiff, 2 ) + Math.pow( yDiff, 2 ) ); } } //Dire outerLoop: for (final Hero h : dire) { for (final Hero i : dire) { if (h == i) { continue outerLoop; } final int xDiff = i.getX( seconds ) - h.getX( seconds ); final int yDiff = i.getY( seconds ) - h.getY( seconds ); baddyDistance += Math.sqrt( Math.pow( xDiff, 2 ) + Math.pow( yDiff, 2 ) ); } } //Average goodDistance /= 5l; baddyDistance /= 5l; goodGuys.add( new FixedMillisecond( seconds ), goodDistance ); badGuys.add( new FixedMillisecond( seconds ), baddyDistance ); } series.addSeries( badGuys ); series.addSeries( goodGuys ); return series; } public static JFreeChart createAverageTeamDistanceGraph( AppState state ) { final JFreeChart chart = ChartFactory.createTimeSeriesChart( GameStatisticsComponent.AVERAGE_TEAM_DISTANCE, // chart title Statics.MILISECONDS, // x axis label "", // y axis label createAverageTeamDistanceDataSet( state ), // data true, // include legend true, // tooltips false // urls ); final XYPlot plot = chart.getXYPlot(); final DateAxis domainAxis = new DateAxis( Statics.TIME ); domainAxis.setTickMarkPosition( DateTickMarkPosition.MIDDLE ); domainAxis.setLowerMargin( 0.0 ); domainAxis.setUpperMargin( 0.0 ); plot.setDomainAxis( domainAxis ); plot.setForegroundAlpha( 0.5f ); plot.setDomainPannable( false ); plot.setRangePannable( false ); final NumberAxis rangeAxis = new NumberAxis( GameStatisticsComponent.AVERAGE_TEAM_DISTANCE ); rangeAxis.setLowerMargin( 0.15 ); rangeAxis.setUpperMargin( 0.15 ); plot.setRangeAxis( rangeAxis ); return chart; } public static XYSeries createDeathMap( String name, AppState appState ) { final Player p = appState.getPlayerByName( name ); final Hero hero = p.getHero(); final Collection<int[]> coords = hero.getDeaths().values(); final XYSeries ret = new XYSeries( name, false, true ); for (final int[] e : coords) { ret.add( e[0], e[1] ); } return ret; } public static String[][] createItemLog( Player p ) { final List<Object[]> log = new ArrayList(); final Hero h = p.getHero(); for (final Dota2Item a : h.getAllItems()) { for (final long l : a.getUsage()) { final Object[] o = new Object[4]; o[0] = l; o[1] = h.getX( l ); o[2] = h.getY( l ); o[3] = a.getKey(); log.add( o ); } } Collections.sort( log, new Comparator<Object[]>() { @Override public int compare( Object[] o1, Object[] o2 ) { final long l1 = (long) o1[0]; final long l2 = (long) o2[0]; return Long.compare( l1, l2 ); } } ); final String[][] ret = new String[log.size()][4]; for (int i = 0; i < log.size(); i++) { final Object[] o = log.get( i ); ret[i][0] = o[0].toString(); ret[i][1] = o[1].toString(); ret[i][2] = o[2].toString(); ret[i][3] = o[3].toString(); } return ret; } public static XYSeries createItemMap( Hero hero, String itemKey ) { final Set<Dota2Item> items = hero.getItemsByName( itemKey ); final XYSeries ret = new XYSeries( hero.getName() + itemKey + MapComponent.CAT_ABILITIES, false, true ); for (final Dota2Item i : items) { for (final Long l : i.getUsage()) { ret.add( hero.getX( l ), hero.getY( l ) ); } } return ret; } public static String[][] createMoveLog( String string, AppState appState ) { final Player p = appState.getPlayerByName( string ); final Unit hero = p.getHero(); final Map<Long, Integer> x = hero.getX(); final Map<Long, Integer> y = hero.getY(); final String[][] ret = new String[x.size()][]; int i = 0; for (final Map.Entry<Long, Integer> e : x.entrySet()) { ret[i] = new String[] { e.getKey().toString(), e.getValue().toString(), y.get( e.getKey() ).toString() }; i++; } return ret; } public static XYSeries createMoveMap( String string, AppState appState ) { final Player p = appState.getPlayerByName( string ); final Unit hero = p.getHero(); final Map<Long, Integer> x = hero.getX(); final Map<Long, Integer> y = hero.getY(); final XYSeries ret = new XYSeries( string, false, true ); for (final Map.Entry<Long, Integer> e : x.entrySet()) { ret.add( e.getValue(), y.get( e.getKey() ) ); } return ret; } private static TimeSeriesCollection createPlayerDataSet( String attribute, List<String> players, AppState appState ) { final TimeSeriesCollection series = new TimeSeriesCollection(); try { for (final String player : players) { final Player p = appState.getPlayerByName( player ); final TimeSeries series1 = new TimeSeries( player ); switch (attribute) { case Statics.EXPERIENCE: for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { series1.add( new FixedMillisecond( seconds ), p.getXP( seconds ) ); } break; case Statics.GOLD: for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { series1.add( new FixedMillisecond( seconds ), p.getEarnedGold( seconds ) ); } break; case Statics.DEATHS: for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { series1.add( new FixedMillisecond( seconds ), p.getDeaths( seconds ).size() ); } break; default: for (final Entry<Long, Map<String, Object>> e : appState.gameEventsPerMs.entrySet()) { if (e.getValue().containsKey( attribute + "." + ID_TO_GAMEEVENT_FORMAT.format( p.getId() ) )) { final Number v = (Number) e.getValue().get( attribute + "." + ID_TO_GAMEEVENT_FORMAT.format( p.getId() ) ); series1.add( new FixedMillisecond( e.getKey() ), v ); } } break; } series.addSeries( series1 ); } } catch (final ClassCastException e) { LOGGER.warning( "Selected attribute contained alpha-numeric data" ); } return series; } public static JFreeChart createPlayerHistogram( String selectedItem, List<String> selectedValuesList, AppState state ) { final JFreeChart chart = ChartFactory.createXYLineChart( selectedItem, // chart title Statics.MILISECONDS, // x axis label "", // y axis label createPlayerDataSet( selectedItem, selectedValuesList, state ), // data PlotOrientation.VERTICAL, true, // include legend true, // tooltips false // urls ); final XYPlot plot = chart.getXYPlot(); final DateAxis domainAxis = new DateAxis( Statics.TIME ); domainAxis.setTickMarkPosition( DateTickMarkPosition.MIDDLE ); domainAxis.setLowerMargin( 0.0 ); domainAxis.setUpperMargin( 0.0 ); plot.setDomainAxis( domainAxis ); plot.setForegroundAlpha( 0.5f ); plot.setDomainPannable( false ); plot.setRangePannable( false ); final NumberAxis rangeAxis = new NumberAxis( selectedItem ); rangeAxis.setLowerMargin( 0.15 ); rangeAxis.setUpperMargin( 0.15 ); plot.setRangeAxis( rangeAxis ); return chart; } private static TimeSeriesCollection createTeamGoldDiffDataSet( AppState appState ) { final TimeSeriesCollection series = new TimeSeriesCollection(); final TimeSeries goodGuys = new TimeSeries( Statics.RADIANT ); final TimeSeries badGuys = new TimeSeries( Statics.DIRE ); final List<Player> radiant = new ArrayList<Player>(); final List<Player> dire = new ArrayList<Player>(); for (final Player p : appState.getPlayers()) { if (p.isRadiant()) { radiant.add( p ); } else { dire.add( p ); } } for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { long baddyGold = 0l; long goodGold = 0l; //Radiant for (final Player p : radiant) { goodGold += p.getEarnedGold( seconds ); } //Dire for (final Player p : dire) { baddyGold += p.getEarnedGold( seconds ); } goodGuys.add( new FixedMillisecond( seconds ), goodGold ); badGuys.add( new FixedMillisecond( seconds ), baddyGold ); } series.addSeries( badGuys ); series.addSeries( goodGuys ); return series; } public static JFreeChart createTeamGoldDifferenceGraph( AppState appState ) { final JFreeChart chart = ChartFactory.createTimeSeriesChart( GameStatisticsComponent.TEAM_XP, Statics.EXPERIENCE, "Time", createTeamGoldDiffDataSet( appState ), true, // legend true, // tool tips false // URLs ); final XYDifferenceRenderer renderer = new XYDifferenceRenderer( Color.GREEN, Color.RED, false ); renderer.setSeriesPaint( 0, Color.GREEN ); renderer.setSeriesPaint( 1, Color.RED ); final XYPlot plot = chart.getXYPlot(); plot.setRenderer( renderer ); final DateAxis domainAxis = new DateAxis( Statics.TIME ); domainAxis.setTickMarkPosition( DateTickMarkPosition.MIDDLE ); domainAxis.setLowerMargin( 0.0 ); domainAxis.setUpperMargin( 0.0 ); plot.setDomainAxis( domainAxis ); plot.setForegroundAlpha( 0.5f ); final NumberAxis rangeAxis = new NumberAxis( Statics.GOLD ); rangeAxis.setLowerMargin( 0.15 ); rangeAxis.setUpperMargin( 0.15 ); plot.setRangeAxis( rangeAxis ); return chart; } private static TimeSeriesCollection createTeamXPDiffDataSet( AppState appState ) { final TimeSeriesCollection series = new TimeSeriesCollection(); final TimeSeries goodGuys = new TimeSeries( Statics.RADIANT ); final TimeSeries badGuys = new TimeSeries( Statics.DIRE ); final List<Player> radiant = new ArrayList<Player>(); final List<Player> dire = new ArrayList<Player>(); for (final Player p : appState.getPlayers()) { if (p.isRadiant()) { radiant.add( p ); } else { dire.add( p ); } } for (long seconds = 0l; seconds < appState.getGameLength(); seconds += appState.getMsPerTick() * 1000) { long baddyXP = 0l; long goodXP = 0l; //Radiant for (final Player p : radiant) { goodXP += p.getXP( seconds ); } //Dire for (final Player p : dire) { baddyXP += p.getXP( seconds ); } goodGuys.add( new FixedMillisecond( seconds ), goodXP ); badGuys.add( new FixedMillisecond( seconds ), baddyXP ); } series.addSeries( badGuys ); series.addSeries( goodGuys ); return series; } public static JFreeChart createTeamXpDifferenceGraph( AppState state ) { final JFreeChart chart = ChartFactory.createTimeSeriesChart( GameStatisticsComponent.TEAM_XP, Statics.EXPERIENCE, "Time", createTeamXPDiffDataSet( state ), true, // legend true, // tool tips false // URLs ); final XYDifferenceRenderer renderer = new XYDifferenceRenderer( Color.GREEN, Color.RED, false ); renderer.setSeriesPaint( 0, Color.GREEN ); renderer.setSeriesPaint( 1, Color.RED ); final XYPlot plot = chart.getXYPlot(); plot.setRenderer( renderer ); final DateAxis domainAxis = new DateAxis( Statics.TIME ); domainAxis.setTickMarkPosition( DateTickMarkPosition.MIDDLE ); domainAxis.setLowerMargin( 0.0 ); domainAxis.setUpperMargin( 0.0 ); plot.setDomainAxis( domainAxis ); plot.setForegroundAlpha( 0.5f ); final NumberAxis rangeAxis = new NumberAxis( Statics.EXPERIENCE ); rangeAxis.setLowerMargin( 0.15 ); rangeAxis.setUpperMargin( 0.15 ); plot.setRangeAxis( rangeAxis ); return chart; } public static String[][] createZoneLog( String name, AppState state ) { final Player p = state.getPlayerByName( name ); final Unit hero = p.getHero(); final Map<Long, Zone> zones = hero.getZones(); final String[][] ret = new String[zones.size()][]; int i = 0; for (final Entry<Long, Zone> e : zones.entrySet()) { ret[i] = new String[] { e.getKey().toString(), e.getValue().name() }; i++; } return ret; } private final static Logger LOGGER = Logger.getLogger( ChartCreator.class.getName() ); /** * TODO * We store player id as a real int, but unhandled game events are stored as name.XXXX. * We temporaily solve this by expanding the real id to four digits. */ private final static DecimalFormat ID_TO_GAMEEVENT_FORMAT = new DecimalFormat( "0000" ); /** * Default constructor to prevent instantiation. */ private ChartCreator() { } }