From 5e332bfac3dfe2075dde30568498bc6c42ca27ce Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Mon, 13 May 2013 00:35:11 +1200 Subject: Move served files to a public/ directory --- .gitmodules | 6 +- css/style.css | 134 ------- index.html | 96 ----- index.js | 7 +- js/comp/item.js | 809 ---------------------------------------- js/comp/makeitem.sed | 16 - js/comp/makemap.sed | 17 - js/comp/makemob.sed | 14 - js/comp/map.js | 114 ------ js/comp/mob.js | 101 ----- js/comp/stat.js | 153 -------- js/crossfilter | 1 - js/d3 | 1 - js/dc | 1 - js/mv/chart.js | 131 ------- js/mv/connect.js | 156 -------- js/mv/heap.js | 63 ---- js/mv/load.js | 60 --- js/mv/main.js | 59 --- js/mv/parse.js | 109 ------ js/util/memoize.js | 45 --- js/util/progress.js | 37 -- js/util/trellis-chart.js | 140 ------- public/css/style.css | 134 +++++++ public/index.html | 96 +++++ public/js/comp/item.js | 809 ++++++++++++++++++++++++++++++++++++++++ public/js/comp/makeitem.sed | 16 + public/js/comp/makemap.sed | 17 + public/js/comp/makemob.sed | 14 + public/js/comp/map.js | 114 ++++++ public/js/comp/mob.js | 101 +++++ public/js/comp/stat.js | 153 ++++++++ public/js/crossfilter | 1 + public/js/d3 | 1 + public/js/dc | 1 + public/js/mv/chart.js | 131 +++++++ public/js/mv/connect.js | 156 ++++++++ public/js/mv/heap.js | 63 ++++ public/js/mv/load.js | 60 +++ public/js/mv/main.js | 59 +++ public/js/mv/parse.js | 109 ++++++ public/js/util/memoize.js | 45 +++ public/js/util/progress.js | 37 ++ public/js/util/trellis-chart.js | 140 +++++++ 44 files changed, 2262 insertions(+), 2265 deletions(-) delete mode 100644 css/style.css delete mode 100644 index.html delete mode 100644 js/comp/item.js delete mode 100755 js/comp/makeitem.sed delete mode 100755 js/comp/makemap.sed delete mode 100755 js/comp/makemob.sed delete mode 100644 js/comp/map.js delete mode 100644 js/comp/mob.js delete mode 100644 js/comp/stat.js delete mode 160000 js/crossfilter delete mode 160000 js/d3 delete mode 160000 js/dc delete mode 100644 js/mv/chart.js delete mode 100644 js/mv/connect.js delete mode 100644 js/mv/heap.js delete mode 100644 js/mv/load.js delete mode 100644 js/mv/main.js delete mode 100644 js/mv/parse.js delete mode 100644 js/util/memoize.js delete mode 100644 js/util/progress.js delete mode 100644 js/util/trellis-chart.js create mode 100644 public/css/style.css create mode 100644 public/index.html create mode 100644 public/js/comp/item.js create mode 100755 public/js/comp/makeitem.sed create mode 100755 public/js/comp/makemap.sed create mode 100755 public/js/comp/makemob.sed create mode 100644 public/js/comp/map.js create mode 100644 public/js/comp/mob.js create mode 100644 public/js/comp/stat.js create mode 160000 public/js/crossfilter create mode 160000 public/js/d3 create mode 160000 public/js/dc create mode 100644 public/js/mv/chart.js create mode 100644 public/js/mv/connect.js create mode 100644 public/js/mv/heap.js create mode 100644 public/js/mv/load.js create mode 100644 public/js/mv/main.js create mode 100644 public/js/mv/parse.js create mode 100644 public/js/util/memoize.js create mode 100644 public/js/util/progress.js create mode 100644 public/js/util/trellis-chart.js diff --git a/.gitmodules b/.gitmodules index 4f540a0..d719d48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "js/crossfilter"] - path = js/crossfilter + path = public/js/crossfilter url = git://github.com/square/crossfilter.git [submodule "js/d3"] - path = js/d3 + path = public/js/d3 url = git://github.com/mbostock/d3.git [submodule "js/dc"] - path = js/dc + path = public/js/dc url = git://github.com/Freeyorp/dc.js.git diff --git a/css/style.css b/css/style.css deleted file mode 100644 index 2690b1f..0000000 --- a/css/style.css +++ /dev/null @@ -1,134 +0,0 @@ -/* Ensure sane defaults */ - -body { - padding: 0; - margin: 0; -} - -/* Top-level layout */ -.side { - float: right; -} -#main { - overflow: hidden; -} - -#mask { - position: absolute; - width: 100%; - height: 100%; - background-color: #333; - opacity: .8; - -moz-transition: opacity 1s linear; - -o-transition: opacity 1s linear; - -webkit-transition: opacity 1s linear; - transition: opacity 1s linear; - top: 0; -} - -body, #main, #status, .side { - min-height: 40px; -} - -body { - min-width: 1350px; -} - -#status { - width: 100%; - font-size: smaller; -} - -/* Columns */ - -.med { - width: 400px; -} - -.thin { - width: 250px; -} - -#main, .side { - font-size: smaller; -} - -/* Titles */ -h3 { - margin: 0.3em 0.8em; -} - -/* Loadinfo panel */ - -#loadinfo { - position: absolute; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - width: 647px; - height: 400px; - border: 1px grey solid; - margin: auto; - background-color: #fff; - padding: 20px; -} - -/* Hide charts while loadinfo is shown */ - -.vis-hide { - display: none; - opacity: 0; -} - -/* Loading bars */ -.progressbar { - margin: 10px 0; - padding: 3px; - border: 1px solid #000; - font-size: 14px; - clear: both; - opacity: 0; -} - -.progressbar.loading { - opacity: 1.0; -} - -.progressbar .percent { - background-color: #99ccff; - height: auto; - width: 0; - white-space: nowrap; -} - -/* Stat chart */ - -#stat-chart svg g g.column .border-line { - fill: none; - stroke: #ccc; - opacity: .5; - shape-rendering: crispEdges; -} - -/* User list */ - -#users-status { - padding: 0; -} - -#users-status li.user { - list-style: none; -} - -/* Utility */ -.fader { - -moz-transition: opacity 1s linear; - -o-transition: opacity 1s linear; - -webkit-transition: opacity 1s linear; - transition: opacity 1s linear; -} - -.help { - cursor: help; -} diff --git a/index.html b/index.html deleted file mode 100644 index baebbb2..0000000 --- a/index.html +++ /dev/null @@ -1,96 +0,0 @@ - - -Manavis - - - - -
-
-
-

Users

-
    -
    -
    -

    Instance breakdown by Character Base Level

    -
    -
    -

    Instance breakdown by Stat allocation

    -
    -
    -
    -
    -
    -
    -

    Instance breakdown by Target

    -
    -
    -

    Instance breakdown by Weapon

    -
    -
    -

    Instance breakdown by Type

    -
    -
    -

    Definedness of records [?]

    -
    -
    -
    -
    - -
    -
    -

    Breakdown by Map [?]

    -
    -
    -

    Instance breakdown by Date

    -
    -
    -

    Instance breakdown by Character ID [?]

    -
    -
    -
    -
    -
    -

    Manavis

    - - -

    Select records to load and display

    -

    You can load any number of files at once.

    - - -
    -
    0%
    -
    -
    -
    0%
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/index.js b/index.js index 2c3e894..5b6c74f 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ app.configure(function () { app.use(cookieParser); app.use(express.session({ store: sessionStore })); app.use(app.router); - app.use(express.static(__dirname)); + app.use(express.static(__dirname + "/public")); }); var server = http.createServer(app) @@ -88,10 +88,7 @@ sessionSockets.on('connection', function (err, socket, session) { }); }); function logAction(action, content) { - logger.info(session.nid - , action - , content - ); + logger.info(session.nid, action, content); } }); diff --git a/js/comp/item.js b/js/comp/item.js deleted file mode 100644 index 65596be..0000000 --- a/js/comp/item.js +++ /dev/null @@ -1,809 +0,0 @@ -var item = function(){ - var item = {}; - var items = { - 0:"DEFAULT", - 501:"CactusDrink", - 502:"CactusPotion", - 503:"CasinoCoins", - 504:"DecorCandy", - 505:"MaggotSlime", - 506:"CandyCane", - 507:"ScorpionStinger", - 508:"XmasCake", - 509:"ChocolateBar", - 510:"Candy", - 511:"SantaHat", - 512:"GingerBreadMan", - 513:"Cake", - 514:"XmasCandyCane", - 515:"PurplePresentBox", - 516:"BluePresentBox", - 517:"RedScorpionStinger", - 518:"BugLeg", - 519:"CherryCake", - 520:"EasterEgg", - 521:"Dagger", - 522:"SharpKnife", - 523:"LeatherShirt", - 524:"FancyHat", - 525:"MinersHat", - 526:"CoinBag", - 527:"Milk", - 528:"Boots", - 529:"IronArrow", - 530:"ShortBow", - 531:"MinerGloves", - 532:"LeatherGloves", - 533:"RoastedMaggot", - 534:"OrangeCupcake", - 535:"RedApple", - 536:"ShortSword", - 537:"TreasureKey", - 538:"GreenPresentBox", - 539:"Beer", - 540:"EmptyBottle", - 541:"BottleOfWater", - 542:"BottleOfSand", - 543:"StandardHeadband", - 544:"SilkHeadband", - 545:"ForestBow", - 546:"DesertShirt", - 547:"Bardiche", - 548:"Halberd", - 549:"Axe", - 550:"BlacksmithsAxe", - 551:"AquaHint", - 552:"MagentaHint", - 553:"YellowHint", - 554:"GreenHint", - 555:"TealHint", - 556:"PurpleHint", - 557:"RedHint", - 558:"BlueHint", - 559:"OrangeHint", - 560:"GrayHint", - 561:"Sabre", - 562:"ChickenLeg", - 563:"WinterGloves", - 564:"TurtleneckSweater", - 565:"PinkPetal", - 566:"SmallMushroom", - 567:"IronPotion", - 568:"ConcentrationPotion", - 569:"RawLog", - 570:"BoneKnife", - 571:"Setzer", - 572:"Scimitar", - 573:"Falchion", - 574:"Scorpion", - 575:"DesertBow", - 576:"Beheader", - 577:"BoneDarts", - 578:"SandCutter", - 579:"RockKnife", - 580:"StaffOfLife", - 581:"CrescentRod", - 582:"StaffOfFire", - 583:"StaffOfIce", - 584:"Jackal", - 585:"ScarabArmlet", - 586:"CottonShorts", - 587:"Sword", - 594:"Spear", - 601:"SteelShield", - 602:"WoodenShield", - 603:"LeatherShield", - 610:"JeansShorts", - 611:"WhiteFur", - 612:"CaveSnakeLamp", - 613:"HardSpike", - 614:"PinkAntenna", - 615:"PumpkinHelmet", - 616:"AxeHat", - 617:"PirateHat", - 618:"Goggles", - 619:"LeatherGoggles", - 620:"Circlet", - 621:"Eyepatch", - 622:"Bandana", - 623:"Scythe", - 624:"VNeckSweater", - 625:"ChainmailShirt", - 626:"LightPlatemail", - 627:"TopHat", - 628:"FunkyHat", - 629:"MushHat", - 630:"ShroomHat", - 631:"DarkCrystal", - 632:"CottonSkirt", - 633:"ChristmasElfHat", - 634:"FaceMask", - 635:"SantaCookie", - 636:"WarlordHelmet", - 637:"KnightsHelmet", - 638:"InfantryHelmet", - 639:"CrusadeHelmet", - 640:"IronOre", - 641:"SnakeSkin", - 642:"JeansChaps", - 643:"WhiteCowboyHat", - 644:"BlackCowboyHat", - 645:"GoldenPlatemail", - 646:"Crown", - 647:"DevelopersCap", - 648:"CottonTrousers", - 649:"WhiteEvokersRobeBlue", - 650:"BlackEvokersRobeBlue", - 651:"WhiteWizardRobe", - 652:"BlackWizardRobe", - 653:"ApprenticeRobe", - 654:"Cap", - 655:"FurBoots", - 656:"SerfHat", - 657:"Orange", - 658:"WarlordPlate", - 659:"GoldenWarlordPlate", - 660:"CottonCloth", - 661:"RedRose", - 662:"WhiteRose", - 663:"DarkRedRose", - 664:"PinkRose", - 665:"YellowRose", - 666:"BlackRose", - 667:"OrangeRose", - 668:"BlueRose", - 669:"YellowTulip", - 670:"PurpleTulip", - 671:"RedTulip", - 672:"WhiteTulip", - 673:"PinkTulip", - 674:"OrangeTulip", - 675:"GraduationCap", - 676:"Steak", - 677:"HeartNecklace", - 678:"NohMask", - 679:"DemonMask", - 680:"MauveHerb", - 681:"CobaltHerb", - 682:"GambogeHerb", - 683:"AlizarinHerb", - 684:"TinyHealingPotion", - 685:"SmallHealingPotion", - 686:"MediumHealingPotion", - 687:"LargeHealingPotion", - 688:"TankTop", - 689:"ShortTankTop", - 690:"RedDye", - 691:"GreenDye", - 692:"DarkBlueDye", - 693:"YellowDye", - 694:"LightBlueDye", - 695:"PinkDye", - 696:"BlackDye", - 697:"OrangeDye", - 698:"PurpleDye", - 699:"DarkGreenDye", - 700:"Pearl", - 701:"PileOfAsh", - 702:"WeddingRing", - 703:"SulphurPowder", - 704:"IronPowder", - 705:"ManaPotion", - 706:"GoldenScorpionStinger", - 707:"MonsterOilPotion", - 708:"LeatherPatch", - 709:"BlackScorpionStinger", - 710:"SnakeTongue", - 711:"MountainSnakeTongue", - 712:"GrassSnakeTongue", - 713:"CaveSnakeTongue", - 714:"SnakeEgg", - 715:"MountainSnakeEgg", - 716:"GrassSnakeEgg", - 717:"CaveSnakeEgg", - 718:"SilkCocoon", - 719:"GreenApple", - 720:"SilkRobe", - 721:"HighPriestCrown", - 722:"MonsterSkullHelmet", - 723:"DesertHat", - 724:"CottonHeadband", - 725:"GMCap", - 726:"GMRobe", - 727:"Iten", - 728:"MoubooFigurine", - 729:"WarpedLog", - 730:"Lifestone", - 731:"AssassinPants", - 732:"DruidTreeBranch", - 733:"PurificationPotion", - 734:"BlackBoots", - 735:"CottonBoots", - 736:"WhiteCake", - 737:"ChocolateCake", - 738:"OrangeCake", - 739:"AppleCake", - 740:"Root", - 741:"CottonGloves", - 742:"FourLeafClover", - 743:"Acorn", - 744:"DilutedConcentrationPotion", - 745:"DarkConcentrationPotion", - 746:"MopoxCurePotion", - 747:"LacedChocolateCake", - 748:"LacedOrangeCupcake", - 749:"Towel", - 750:"SlowPoisonPotion", - 751:"PinkieHat", - 752:"FluffyHat", - 753:"BatWing", - 754:"BatTeeth", - 755:"AssassinShirt", - 756:"AssassinGloves", - 757:"AssassinBoots", - 758:"WoodenStaff", - 762:"TerraniteArrow", - 763:"TerraniteOre", - 766:"TerraniteHelmet", - 767:"TerraniteChestArmor", - 768:"TerraniteLegs", - 769:"GuyFawkesMask", - 770:"FairyHat", - 771:"Miniskirt", - 772:"WispPowder", - 773:"SpectrePowder", - 774:"PoltergeistPowder", - 775:"Bone", - 776:"Skull", - 777:"RottenRags", - 778:"DiseasedHeart", - 779:"UndeadEar", - 780:"UndeadEye", - 782:"ForestArmor", - 783:"PlatynaRedDress", - 784:"ZombieNachos", - 785:"LadyFingers", - 786:"JellAhh", - 787:"Snapple", - 788:"BeetleJuice", - 789:"GutBuster", - 790:"BloodWine", - 791:"YetiSkinShirt", - 792:"BromenalBoots", - 793:"BromenalChest", - 794:"BromenalGloves", - 795:"BromenalHelmet", - 796:"BromenalLegs", - 797:"BromenalShield", - 798:"SorcererRobeRed", - 799:"MylarinDust", - 800:"BowlerHatBrown", - 801:"PinkieHelmet", - 802:"EasterBasket", - 803:"GrassLiner", - 804:"JellyBeans", - 805:"ChocolateMouboo", - 806:"ReedBundle", - 807:"GrassSeed", - 808:"HitchhikersTowel", - 809:"WhiteHitchhikersTowel", - 810:"RedHitchhikersTowel", - 811:"GreenHitchhikersTowel", - 812:"BlueHitchhikersTowel", - 813:"YellowHitchhikersTowel", - 814:"PurpleHitchhikersTowel", - 815:"OrangeHitchhikersTowel", - 816:"PinkHitchhikersTowel", - 817:"TealHitchhikersTowel", - 818:"LimeHitchhikersTowel", - 819:"DiamondPowder", - 820:"RubyPowder", - 821:"EmeraldPowder", - 822:"SapphirePowder", - 823:"TopazPowder", - 824:"AmethystPowder", - 825:"TinyManaElixir", - 826:"SmallManaElixir", - 827:"MediumManaElixir", - 828:"LargeManaElixir", - 829:"CrozeniteFourLeafAmulet", - 830:"BromenalFourLeafAmulet", - 831:"SilverFourLeafAmulet", - 832:"GoldenFourLeafAmulet", - 833:"BrokenFourLeafAmulet", - 834:"BrokenDoll", - 835:"HyvernStinger", - 836:"GrubSlime", - 838:"CranberryLollipop", - 839:"GrapeLollipop", - 840:"OrangeLollipop", - 841:"RedDottedWrap", - 842:"YellowDottedWrap", - 843:"BlueDottedWrap", - 844:"PurpleStripedWrap", - 845:"RedGoldenStripedWrap", - 846:"GreenRedStripedWrap", - 847:"PlushMouboo", - 848:"Earmuffs", - 849:"OpenPresentBox", - 850:"ClosedChristmasBox", - 851:"StickReinboo", - 852:"LeatherBall", - 853:"Doll", - 854:"ElfNightcap", - 855:"Sunglasses", - 856:"KnitCap", - 857:"LeatherTrousers", - 858:"WolvernTooth", - 859:"WolvernPelt", - 860:"SquirrelPelt", - 861:"WhiteBellTuber", - 862:"IcedWater", - 863:"SilverMirror", - 864:"BookPage", - 865:"Grimoire", - 866:"LeatherSuitcase", - 867:"IceGladius", - 868:"SilkGloves", - 869:"Antlers", - 870:"FineDress", - 871:"SealedSoul", - 872:"LockPicks", - 873:"LazuriteShard", - 874:"LazuriteCrystal", - 875:"HeartOfLazurite", - 876:"WarlordBoots", - 877:"BullHelmet", - 878:"BansheeBow", - 879:"HeartOfIsis", - 880:"LazuriteRobe", - 881:"RaggedShorts", - 882:"RedEggshellHat", - 883:"BlueEggshellHat", - 884:"YellowEggshellHat", - 885:"GreenEggshellHat", - 886:"OrangeEggshellHat", - 887:"DarkEggshellHat", - 888:"MagicGMTopHat", - 889:"MurdererCrown", - 890:"BeanieCopter", - 1198:"JackOSoul", - 1199:"Arrow", - 1200:"Bow", - 1201:"Knife", - 1202:"CottonShirt", - 1203:"RangerHat", - 1204:"AntlerHat", - 1205:"ChristmasTreeHat", - 1206:"SantaBeardHat", - 1207:"RedChristmasStocking", - 1208:"RedEasterEgg", - 1209:"GreenEasterEgg", - 1210:"BlueEasterEgg", - 1211:"YellowEasterEgg", - 1212:"PinkEasterEgg", - 1213:"TealEasterEgg", - 1214:"BunnyEars", - 1215:"ToySabre", - 1216:"MoubooHead", - 1217:"CatEars", - 1218:"PaperBag", - 1219:"MoubootaurHead", - 1220:"BunchOfParsley", - 1221:"SkullMask", - 1228:"LightCrystal", - 1229:"CaramelApple", - 1230:"LollipopColor1", - 1231:"LollipopColor2", - 1232:"LollipopColor3", - 1233:"FakeFangs", - 1234:"RedOrnament", - 1235:"YellowOrnament", - 1236:"GreenOrnament", - 1237:"AquaOrnament", - 1238:"BlueOrnament", - 1239:"MagentaOrnament", - 1240:"SantaSnowGlobe", - 1241:"SnowmanSnowGlobe", - 1242:"SnowGoggles", - 1244:"DarkTalisman", - 1245:"BentNeedle", - 1246:"DarkEasterEgg", - 1247:"HeartGlasses", - 1248:"Blueberries", - 1249:"StrangeCoin", - 1250:"Pear", - 1251:"Plum", - 1252:"Cherry", - 1253:"GoldenDeliciousApple", - 1254:"DarkPetal", - 1255:"WhiteRabbitEars", - 1256:"EggshellHat", - 1257:"FlawedLens", - 1258:"Honey", - 1276:"OperaMask", - 1277:"JesterMask", - 1278:"WitchHat", - 1279:"GoblinMask", - 1280:"Scissors", - 1281:"ShockSweet", - 1282:"BoneArrows", - 2050:"RedCottonShirt", - 2051:"GreenCottonShirt", - 2052:"DarkBlueCottonShirt", - 2053:"YellowCottonShirt", - 2054:"LightBlueCottonShirt", - 2055:"PinkCottonShirt", - 2056:"BlackCottonShirt", - 2057:"OrangeCottonShirt", - 2058:"PurpleCottonShirt", - 2059:"DarkGreenCottonShirt", - 2060:"RedVNeckSweater", - 2061:"GreenVNeckSweater", - 2062:"DarkBlueVNeckSweater", - 2063:"YellowVNeckSweater", - 2064:"LightBlueVNeckSweater", - 2065:"PinkVNeckSweater", - 2066:"BlackVNeckSweater", - 2067:"OrangeVNeckSweater", - 2068:"PurpleVNeckSweater", - 2069:"DarkGreenVNeckSweater", - 2070:"RedTurtleneckSweater", - 2071:"GreenTurtleneckSweater", - 2072:"DarkBlueTurtleneckSweater", - 2073:"YellowTurtleneckSweater", - 2074:"LightBlueTurtleneckSweater", - 2075:"PinkTurtleneckSweater", - 2076:"BlackTurtleneckSweater", - 2077:"OrangeTurtleneckSweater", - 2078:"PurpleTurtleneckSweater", - 2079:"DarkGreenTurtleneckSweater", - 2080:"RedSilkRobe", - 2081:"GreenSilkRobe", - 2082:"DarkBlueSilkRobe", - 2083:"YellowSilkRobe", - 2084:"LightBlueSilkRobe", - 2085:"PinkSilkRobe", - 2086:"BlackSilkRobe", - 2087:"OrangeSilkRobe", - 2088:"PurpleSilkRobe", - 2089:"DarkGreenSilkRobe", - 2090:"RedTankTop", - 2091:"GreenTankTop", - 2092:"DarkBlueTankTop", - 2093:"YellowTankTop", - 2094:"LightBlueTankTop", - 2095:"PinkTankTop", - 2096:"BlackTankTop", - 2097:"OrangeTankTop", - 2098:"PurpleTankTop", - 2099:"DarkGreenTankTop", - 2100:"RedCottonSkirt", - 2101:"GreenCottonSkirt", - 2102:"DarkBlueCottonSkirt", - 2103:"YellowCottonSkirt", - 2104:"LightBlueCottonSkirt", - 2105:"PinkCottonSkirt", - 2106:"BlackCottonSkirt", - 2107:"OrangeCottonSkirt", - 2108:"PurpleCottonSkirt", - 2109:"DarkGreenCottonSkirt", - 2110:"RedCottonShorts", - 2111:"GreenCottonShorts", - 2112:"DarkBlueCottonShorts", - 2113:"YellowCottonShorts", - 2114:"LightBlueCottonShorts", - 2115:"PinkCottonShorts", - 2116:"BlackCottonShorts", - 2117:"OrangeCottonShorts", - 2118:"PurpleCottonShorts", - 2119:"DarkGreenCottonShorts", - 2120:"RedShortTankTop", - 2121:"GreenShortTankTop", - 2122:"DarkBlueShortTankTop", - 2123:"YellowShortTankTop", - 2124:"LightBlueShortTankTop", - 2125:"PinkShortTankTop", - 2126:"BlackShortTankTop", - 2127:"OrangeShortTankTop", - 2128:"PurpleShortTankTop", - 2129:"DarkGreenShortTankTop", - 2130:"RedDesertHat", - 2131:"GreenDesertHat", - 2132:"DarkBlueDesertHat", - 2133:"YellowDesertHat", - 2134:"LightBlueDesertHat", - 2135:"PinkDesertHat", - 2136:"BlackDesertHat", - 2137:"OrangeDesertHat", - 2138:"PurpleDesertHat", - 2139:"DarkGreenDesertHat", - 2140:"RedCottonHeadband", - 2141:"GreenCottonHeadband", - 2142:"DarkBlueCottonHeadband", - 2143:"YellowCottonHeadband", - 2144:"LightBlueCottonHeadband", - 2145:"PinkCottonHeadband", - 2146:"BlackCottonHeadband", - 2147:"OrangeCottonHeadband", - 2148:"PurpleCottonHeadband", - 2149:"DarkGreenCottonHeadband", - 2150:"RedCottonBoots", - 2151:"GreenCottonBoots", - 2152:"DarkBlueCottonBoots", - 2153:"YellowCottonBoots", - 2154:"LightBlueCottonBoots", - 2155:"PinkCottonBoots", - 2156:"BlackCottonBoots", - 2157:"OrangeCottonBoots", - 2158:"PurpleCottonBoots", - 2159:"DarkGreenCottonBoots", - 2160:"RedCottonGloves", - 2161:"GreenCottonGloves", - 2162:"DarkBlueCottonGloves", - 2163:"YellowCottonGloves", - 2164:"LightBlueCottonGloves", - 2165:"PinkCottonGloves", - 2166:"BlackCottonGloves", - 2167:"OrangeCottonGloves", - 2168:"PurpleCottonGloves", - 2169:"DarkGreenCottonGloves", - 2170:"RedMiniskirt", - 2171:"GreenMiniskirt", - 2172:"DarkBlueMiniskirt", - 2173:"YellowMiniskirt", - 2174:"LightBlueMiniskirt", - 2175:"PinkMiniskirt", - 2176:"BlackMiniskirt", - 2177:"OrangeMiniskirt", - 2178:"PurpleMiniskirt", - 2179:"DarkGreenMiniskirt", - 2180:"RedCottonTrousers", - 2181:"GreenCottonTrousers", - 2182:"DarkBlueCottonTrousers", - 2183:"YellowCottonTrousers", - 2184:"LightBlueCottonTrousers", - 2185:"PinkCottonTrousers", - 2186:"BlackCottonTrousers", - 2187:"OrangeCottonTrousers", - 2188:"PurpleCottonTrousers", - 2189:"DarkGreenCottonTrousers", - 2190:"RedRabbitEars", - 2191:"GreenRabbitEars", - 2192:"DarkBlueRabbitEars", - 2193:"YellowRabbitEars", - 2194:"LightBlueRabbitEars", - 2195:"PinkRabbitEars", - 2196:"BlackRabbitEars", - 2197:"OrangeRabbitEars", - 2198:"PurpleRabbitEars", - 2199:"DarkGreenRabbitEars", - 2200:"RedWizardHat", - 2201:"GreenWizardHat", - 2202:"DarkBlueWizardHat", - 2203:"YellowWizardHat", - 2204:"LightBlueWizardHat", - 2205:"PinkWizardHat", - 2206:"BlackWizardHat", - 2207:"OrangeWizardHat", - 2208:"PurpleWizardHat", - 2209:"DarkGreenWizardHat", - 2210:"RedBowlerHat", - 2211:"GreenBowlerHat", - 2212:"DarkBlueBowlerHat", - 2213:"YellowBowlerHat", - 2214:"LightBlueBowlerHat", - 2215:"PinkBowlerHat", - 2216:"BlackBowlerHat", - 2217:"OrangeBowlerHat", - 2218:"PurpleBowlerHat", - 2219:"DarkGreenBowlerHat", - 2220:"RedSorcererRobeRed", - 2221:"GreenSorcererRobeRed", - 2222:"DarkBlueSorcererRobeRed", - 2223:"YellowSorcererRobeRed", - 2224:"LightBlueSorcererRobeRed", - 2225:"PinkSorcererRobeRed", - 2226:"BlackSorcererRobeRed", - 2227:"OrangeSorcererRobeRed", - 2228:"PurpleSorcererRobeRed", - 2229:"DarkGreenSorcererRobeRed", - 2230:"RedBowlerHatBrown", - 2231:"GreenBowlerHatBrown", - 2232:"DarkBlueBowlerHatBrown", - 2233:"YellowBowlerHatBrown", - 2234:"LightBlueBowlerHatBrown", - 2235:"PinkBowlerHatBrown", - 2236:"BlackBowlerHatBrown", - 2237:"OrangeBowlerHatBrown", - 2238:"PurpleBowlerHatBrown", - 2239:"DarkGreenBowlerHatBrown", - 2240:"FineRedDress", - 2241:"FineGreenDress", - 2242:"FineDarkBlueDress", - 2243:"FineYellowDress", - 2244:"FineLightBlueDress", - 2245:"FinePinkDress", - 2246:"FineBlackDress", - 2247:"FineOrangeDress", - 2248:"FinePurpleDress", - 2249:"FineDarkGreenDress", - 2250:"RedCottonCloth", - 2251:"GreenCottonCloth", - 2252:"DarkBlueCottonCloth", - 2253:"YellowCottonCloth", - 2254:"LightBlueCottonCloth", - 2255:"PinkCottonCloth", - 2256:"BlackCottonCloth", - 2257:"OrangeCottonCloth", - 2258:"PurpleCottonCloth", - 2259:"DarkGreenCottonCloth", - 3000:"JackOLantern", - 3001:"RubberBat", - 3002:"RealisticBrain", - 3003:"JarofBlood", - 3004:"Tongue", - 3006:"TonoriDelight", - 3007:"Marshmallow", - 3009:"JellySkull", - 3010:"CandyPumpkin", - 3011:"PumpkinSeeds", - 4000:"AngryScorpionStinger", - 4001:"Coal", - 4002:"Diamond", - 4003:"Ruby", - 4004:"Emerald", - 4005:"Sapphire", - 4006:"Topaz", - 4007:"Amethyst", - 4008:"DiamondRing", - 4009:"RubyRing", - 4010:"EmeraldRing", - 4011:"SapphireRing", - 4012:"TopazRing", - 4013:"AmethystRing", - 4014:"SimpleRing", - 4015:"IronIngot", - 4016:"BanditHood", - 4017:"RedPowder", - 4018:"YellowPowder", - 4019:"BluePowder", - 4020:"CandleHelmet", - 4021:"YellowPresentBox", - 4022:"WhitePresentBox", - 4023:"AnimalBones", - 4024:"FrozenYetiTear", - 4025:"YetiClaw", - 4026:"IceCube", - 4027:"YetiMask", - 4028:"WizardHat", - 4029:"GrimaceOfDementia", - 4030:"BowlerHat", - 4031:"Monocle", - 4032:"PanHat", - 4033:"ChefHat", - 4034:"BlackPearl", - 4035:"PickledBeets", - 4036:"RoastedAcorn", - 4037:"WhiteBlanket", - 4038:"WhiteSaddleRug", - 4039:"RedSaddleRug", - 4040:"RawTalisman", - 4041:"FlightTalisman", - 4042:"RedNose", - 5000:"RedSorcererRobeGreen", - 5001:"GreenSorcererRobeGreen", - 5002:"DarkBlueSorcererRobeGreen", - 5003:"YellowSorcererRobeGreen", - 5004:"LightBlueSorcererRobeGreen", - 5005:"PinkSorcererRobeGreen", - 5006:"BlackSorcererRobeGreen", - 5007:"OrangeSorcererRobeGreen", - 5008:"PurpleSorcererRobeGreen", - 5009:"DarkGreenSorcererRobeGreen", - 5010:"SorcererRobeGreen", - 5011:"RedSorcererRobeDarkBlue", - 5012:"GreenSorcererRobeDarkBlue", - 5013:"DarkBlueSorcererRobeDarkBlue", - 5014:"YellowSorcererRobeDarkBlue", - 5015:"LightBlueSorcererRobeDarkBlue", - 5016:"PinkSorcererRobeDarkBlue", - 5017:"BlackSorcererRobeDarkBlue", - 5018:"OrangeSorcererRobeDarkBlue", - 5019:"PurpleSorcererRobeDarkBlue", - 5020:"DarkGreenSorcererRobeDarkBlue", - 5021:"SorcererRobeDarkBlue", - 5022:"RedSorcererRobeYellow", - 5023:"GreenSorcererRobeYellow", - 5024:"DarkBlueSorcererRobeYellow", - 5025:"YellowSorcererRobeYellow", - 5026:"LightBlueSorcererRobeYellow", - 5027:"PinkSorcererRobeYellow", - 5028:"BlackSorcererRobeYellow", - 5029:"OrangeSorcererRobeYellow", - 5030:"PurpleSorcererRobeYellow", - 5031:"DarkGreenSorcererRobeYellow", - 5032:"SorcererRobeYellow", - 5033:"RedSorcererRobeLightBlue", - 5034:"GreenSorcererRobeLightBlue", - 5035:"DarkBlueSorcererRobeLightBlue", - 5036:"YellowSorcererRobeLightBlue", - 5037:"LightBlueSorcererRobeLightBlue", - 5038:"PinkSorcererRobeLightBlue", - 5039:"BlackSorcererRobeLightBlue", - 5040:"OrangeSorcererRobeLightBlue", - 5041:"PurpleSorcererRobeLightBlue", - 5042:"DarkGreenSorcererRobeLightBlue", - 5043:"SorcererRobeLightBlue", - 5044:"RedSorcererRobePink", - 5045:"GreenSorcererRobePink", - 5046:"DarkBlueSorcererRobePink", - 5047:"YellowSorcererRobePink", - 5048:"LightBlueSorcererRobePink", - 5049:"PinkSorcererRobePink", - 5050:"BlackSorcererRobePink", - 5051:"OrangeSorcererRobePink", - 5052:"PurpleSorcererRobePink", - 5053:"DarkGreenSorcererRobePink", - 5054:"SorcererRobePink", - 5055:"RedSorcererRobeBlack", - 5056:"GreenSorcererRobeBlack", - 5057:"DarkBlueSorcererRobeBlack", - 5058:"YellowSorcererRobeBlack", - 5059:"LightBlueSorcererRobeBlack", - 5060:"PinkSorcererRobeBlack", - 5061:"BlackSorcererRobeBlack", - 5062:"OrangeSorcererRobeBlack", - 5063:"PurpleSorcererRobeBlack", - 5064:"DarkGreenSorcererRobeBlack", - 5065:"SorcererRobeBlack", - 5066:"RedSorcererRobeOrange", - 5067:"GreenSorcererRobeOrange", - 5068:"DarkBlueSorcererRobeOrange", - 5069:"YellowSorcererRobeOrange", - 5070:"LightBlueSorcererRobeOrange", - 5071:"PinkSorcererRobeOrange", - 5072:"BlackSorcererRobeOrange", - 5073:"OrangeSorcererRobeOrange", - 5074:"PurpleSorcererRobeOrange", - 5075:"DarkGreenSorcererRobeOrange", - 5076:"SorcererRobeOrange", - 5077:"RedSorcererRobePurple", - 5078:"GreenSorcererRobePurple", - 5079:"DarkBlueSorcererRobePurple", - 5080:"YellowSorcererRobePurple", - 5081:"LightBlueSorcererRobePurple", - 5082:"PinkSorcererRobePurple", - 5083:"BlackSorcererRobePurple", - 5084:"OrangeSorcererRobePurple", - 5085:"PurpleSorcererRobePurple", - 5086:"DarkGreenSorcererRobePurple", - 5087:"SorcererRobePurple", - 5088:"RedSorcererRobeDarkGreen", - 5089:"GreenSorcererRobeDarkGreen", - 5090:"DarkBlueSorcererRobeDarkGreen", - 5091:"YellowSorcererRobeDarkGreen", - 5092:"LightBlueSorcererRobeDarkGreen", - 5093:"PinkSorcererRobeDarkGreen", - 5094:"BlackSorcererRobeDarkGreen", - 5095:"OrangeSorcererRobeDarkGreen", - 5096:"PurpleSorcererRobeDarkGreen", - 5097:"DarkGreenSorcererRobeDarkGreen", - 5098:"SorcererRobeDarkGreen", - 5099:"RedSorcererRobeWhite", - 5100:"GreenSorcererRobeWhite", - 5101:"DarkBlueSorcererRobeWhite", - 5102:"YellowSorcererRobeWhite", - 5103:"LightBlueSorcererRobeWhite", - 5104:"PinkSorcererRobeWhite", - 5105:"BlackSorcererRobeWhite", - 5106:"OrangeSorcererRobeWhite", - 5107:"PurpleSorcererRobeWhite", - 5108:"DarkGreenSorcererRobeWhite", - 5109:"SorcererRobeWhite", - }; - item.nameByServerID = function(serverID) { - return serverID in items ? items[serverID] : "UNDEFINED"; - } - return item; -}(); diff --git a/js/comp/makeitem.sed b/js/comp/makeitem.sed deleted file mode 100755 index 45e5a91..0000000 --- a/js/comp/makeitem.sed +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sed -nf -# Usage: ./makeitem.sed < item.in > item.js -1i\ -var item = function(){\ - var item = {};\ - var items = { -# /(? map.js -1i\ -var map = function(){\ - var map = {};\ - var maps = { -/^Loading Maps/,/^Maps Loaded/ { - s/^Loading Maps \[\([0-9]\+\)\/[0-9]\+\]: data\\\(.*\)\.gat/ "\1": "\2",/p; -} -$i\ - };\ - map.nameByServerID = function(serverID, date) {\ - /* TODO: Merged output format suitable for converting records running under different data */\ - return maps[serverID];\ - }\ - return map;\ -}(); diff --git a/js/comp/makemob.sed b/js/comp/makemob.sed deleted file mode 100755 index c934a4e..0000000 --- a/js/comp/makemob.sed +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sed -nf -# Usage: ./makemob.sed < mob.in > mob.js -1i\ -var mob = function(){\ - var mob = {};\ - var mobs = { -s/^\([0-9]\+\),[\t ]\+\([^\t ]\+\),.*/ \1:"\2",/p -$i\ - };\ - mob.nameByServerID = function(serverID) {\ - return serverID in mobs ? mobs[serverID] : "UNDEFINED";\ - }\ - return mob;\ -}(); diff --git a/js/comp/map.js b/js/comp/map.js deleted file mode 100644 index d845031..0000000 --- a/js/comp/map.js +++ /dev/null @@ -1,114 +0,0 @@ -var map = function(){ - var map = {}; - var maps = { - "0": "001-1", - "1": "001-2", - "2": "001-3", - "3": "002-1", - "4": "002-3", - "5": "002-4", - "6": "003-1", - "7": "003-2", - "8": "004-1", - "9": "004-2", - "10": "005-1", - "11": "005-3", - "12": "005-4", - "13": "006-1", - "14": "006-3", - "15": "007-1", - "16": "008-1", - "17": "009-1", - "18": "009-2", - "19": "009-3", - "20": "009-4", - "21": "009-5", - "22": "009-6", - "23": "010-1", - "24": "010-2", - "25": "011-1", - "26": "011-3", - "27": "011-4", - "28": "011-6", - "29": "012-1", - "30": "012-3", - "31": "012-4", - "32": "013-1", - "33": "013-2", - "34": "013-3", - "35": "014-1", - "36": "014-3", - "37": "015-1", - "38": "015-3", - "39": "016-1", - "40": "017-1", - "41": "017-2", - "42": "017-3", - "43": "017-4", - "44": "017-9", - "45": "018-1", - "46": "018-2", - "47": "018-3", - "48": "019-1", - "49": "019-3", - "50": "019-4", - "51": "020-1", - "52": "020-2", - "53": "020-3", - "54": "021-1", - "55": "021-2", - "56": "022-1", - "57": "024-1", - "58": "024-2", - "59": "024-3", - "60": "024-4", - "61": "025-1", - "62": "025-3", - "63": "025-4", - "64": "026-1", - "65": "027-1", - "66": "027-2", - "67": "027-3", - "68": "027-4", - "69": "028-1", - "70": "028-3", - "71": "029-1", - "72": "029-3", - "73": "030-1", - "74": "030-2", - "75": "031-1", - "76": "031-2", - "77": "031-3", - "78": "031-4", - "79": "032-1", - "80": "032-3", - "81": "033-1", - "82": "034-1", - "83": "041-1", - "84": "042-1", - "85": "042-2", - "86": "044-1", - "87": "044-3", - "88": "045-1", - "89": "046-1", - "90": "046-3", - "91": "047-1", - "92": "048-1", - "93": "048-2", - "94": "051-1", - "95": "051-3", - "96": "052-1", - "97": "052-2", - "98": "055-1", - "99": "055-3", - "100": "056-1", - "101": "056-2", - "102": "057-1", - "103": "botcheck", - }; - map.nameByServerID = function(serverID, date) { - /* TODO: Merged output format suitable for converting records running under different data */ - return maps[serverID]; - } - return map; -}(); diff --git a/js/comp/mob.js b/js/comp/mob.js deleted file mode 100644 index 022494e..0000000 --- a/js/comp/mob.js +++ /dev/null @@ -1,101 +0,0 @@ -var mob = function(){ - var mob = {}; - var mobs = { - 1002:"Maggot", - 1003:"Scorpion", - 1004:"RedScorpion", - 1005:"GreenSlime", - 1006:"GiantMaggot", - 1007:"YellowSlime", - 1008:"RedSlime", - 1009:"BlackScorpion", - 1010:"Snake", - 1011:"FireGoblin", - 1012:"Spider", - 1013:"EvilMushroom", - 1014:"PinkFlower", - 1015:"SantaSlime", - 1016:"RudolphSlime", - 1017:"Bat", - 1018:"Pinkie", - 1019:"SpikyMushroom", - 1020:"Fluffy", - 1021:"CaveSnake", - 1022:"JackO", - 1023:"FireSkull", - 1024:"PoisonSkull", - 1025:"LogHead", - 1026:"MountainSnake", - 1027:"EasterFluffy", - 1028:"Mouboo", - 1029:"MauvePlant", - 1030:"CobaltPlant", - 1031:"GambogePlant", - 1032:"AlizarinPlant", - 1033:"SeaSlime", - 1034:"GrassSnake", - 1035:"Silkworm", - 1036:"Zombie", - 1037:"CloverPatch", - 1038:"Squirrel", - 1040:"Wisp", - 1041:"Snail", - 1042:"Spectre", - 1043:"Skeleton", - 1044:"LadySkeleton", - 1045:"Fallen", - 1046:"SnakeLord", - 1047:"Poltergeist", - 1049:"Bee", - 1055:"Butterfly", - 1056:"CaveMaggot", - 1057:"AngryScorpion", - 1058:"IceGoblin", - 1059:"GCMaggot", - 1060:"Archant", - 1061:"Moggun", - 1062:"Terranite", - 1063:"Pumpkin", - 1064:"Bandit", - 1065:"BanditLord", - 1066:"VampireBat", - 1067:"Reaper", - 1068:"Reaper2", - 1069:"Scythe", - 1070:"BallLightning", - 1071:"IceElement", - 1072:"Yeti", - 1073:"TheLost", - 1077:"DrunkenSkeleton", - 1078:"TipsySkeleton", - 1079:"DrunkenLadySkeleton", - 1080:"BlueSpark", - 1081:"RedSpark", - 1082:"Serqet", - 1083:"HuntsmanSpider", - 1084:"CrotcherScorpion", - 1085:"IceSkull", - 1086:"FeyElement", - 1087:"Larvern", - 1088:"Hyvern", - 1089:"HungryFluffy", - 1090:"Wolvern", - 1091:"BlueSlime", - 1092:"SlimeBlast", - 1093:"WhiteSlime", - 1094:"Reinboo", - 1095:"WhiteBell", - 1096:"SoulSnake", - 1097:"SoulEater", - 1098:"CopperSlime", - 1099:"SleepingBandit", - 1100:"AzulSlime", - 1101:"DemonicSpirit", - 1102:"Luvia", - 1103:"WitchGuard", - }; - mob.nameByServerID = function(serverID) { - return serverID in mobs ? mobs[serverID] : "UNDEFINED"; - } - return mob; -}(); diff --git a/js/comp/stat.js b/js/comp/stat.js deleted file mode 100644 index cd75646..0000000 --- a/js/comp/stat.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Make computations about tmwAthena's stat requirements. - * - * Amongst other things, this can be used to derive a minimum base level for a stat configuration. - */ -var stat = function(){ - var stat = {}; - var statpoint = [ - 48, - 52, - 56, - 60, - 64, - 69, - 74, - 79, - 84, - 90, - 96, - 102, - 108, - 115, - 122, - 129, - 136, - 144, - 152, - 160, - 168, - 177, - 186, - 195, - 204, - 214, - 224, - 234, - 244, - 255, - 266, - 277, - 288, - 300, - 312, - 324, - 336, - 349, - 362, - 375, - 388, - 402, - 416, - 430, - 444, - 459, - 474, - 489, - 504, - 520, - 536, - 552, - 568, - 585, - 602, - 619, - 636, - 654, - 672, - 690, - 708, - 727, - 746, - 765, - 784, - 804, - 824, - 844, - 864, - 885, - 906, - 927, - 948, - 970, - 992, - 1014, - 1036, - 1059, - 1082, - 1105, - 1128, - 1152, - 1176, - 1200, - 1224, - 1249, - 1274, - 1299, - 1324, - 1350, - 1376, - 1402, - 1428, - 1455, - 1482, - 1509, - 1536, - 1564, - 1592 - ]; - /* If a character is using a certain number of status points, what is the lowest level it could be? */ - stat.minLevelForStatusPoints = function(statusPoints) { - /* - * tmwAthena status points for a level are described by the following recurrence relation: - * p(1) = 48 - * p(l) = p(l - 1) + floor((l + 14) / 4) - * For whatever reason, the server also loads a cached copy from a file of all places - db/statpoint.txt. - * If this is out of sync with the equation, fun things can happen. - * For now, naively assume that statpoint.txt is correct and shamelessly dump it here (see above). - */ - return crossfilter.bisect.left(statpoint, statusPoints, 0, statpoint.length) + 1; - }; - var statusPointInc = [0, 0] - stat.statusPointsForStat = function(v) { - if (isNaN(v)) { - throw "Invalid input"; - } - /* First, convert the absolute stat to terms of increments. */ - /* This is as simple as removing the starting value - 1 - from it. */ - /* - * The status points needed to increase from a stat with value v can be calculated as follows: - * floor((v + 9) / 10) + 1 - */ - if (statusPointInc.length > v) { - return statusPointInc[v]; - } - var i = stat.statusPointsForStat(v - 1) + Math.floor(((v - 1) + 9) / 10) + 1; - statusPointInc.push(i); - return i; - } - /* If a character has a certain arrangement of attributes, how many status points are required for this configuration? */ - stat.statusPointsForStats = function(str, agi, vit, dex, int, luk) { - return stat.statusPointsForStat(str) + - stat.statusPointsForStat(agi) + - stat.statusPointsForStat(vit) + - stat.statusPointsForStat(dex) + - stat.statusPointsForStat(int) + - stat.statusPointsForStat(luk); - }; - /* Helper function currying minLevelForStatusPoints and stat.statusPointsForStats */ - stat.minLevelForStats = function(str, agi, vit, dex, int, luk) { - return stat.minLevelForStatusPoints(stat.statusPointsForStats(str, agi, vit, dex, int, luk)); - }; - return stat; -}(); diff --git a/js/crossfilter b/js/crossfilter deleted file mode 160000 index ae994e8..0000000 --- a/js/crossfilter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae994e8f8014ad4304e0575973bb23c1fb1ac0b7 diff --git a/js/d3 b/js/d3 deleted file mode 160000 index 91d35b4..0000000 --- a/js/d3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 91d35b4205d4ef150c61c415b7379404bced7267 diff --git a/js/dc b/js/dc deleted file mode 160000 index 649860c..0000000 --- a/js/dc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 649860c7f007ae41a89e1d6236748ba36292288f diff --git a/js/mv/chart.js b/js/mv/chart.js deleted file mode 100644 index d06d40e..0000000 --- a/js/mv/chart.js +++ /dev/null @@ -1,131 +0,0 @@ -var mv = function(mv) { - mv.charts = {}; - var thinWidth = 250; - var medWidth = 400; - var wideWidth = Math.max(700, document.width - thinWidth - medWidth); - mv.charter = function() { - var charter = {}; - charter.init = function() { - mv.charts.date = bar(monoGroup(wide(dc.barChart("#date-chart")), "date")) - .centerBar(true) - .elasticX(true) - .x(d3.time.scale().domain([mv.heap.date.dim.bottom(1)[0].date, mv.heap.date.dim.top(1)[0].date]).nice(d3.time.hour)) - .xUnits(d3.time.hours) - .xAxisPadding(2) - ; - mv.charts.pc = bar(monoGroup(wide(dc.barChart("#player-chart")), "pc")) - .x(d3.scale.linear().domain([mv.heap.pc.dim.bottom(1)[0].pc, mv.heap.pc.dim.top(1)[0].pc]).nice()) - ; - mv.charts.blvl = bar(monoGroup(med(dc.barChart("#blvl-chart")), "blvl")) - .x(d3.scale.linear().domain([0, mv.heap.blvl.dim.top(1)[0].pcstat.blvl])) - ; - mv.charts.type = pie(monoGroup(dc.pieChart("#type-chart"), "type")) - ; - mv.charts.target = pie(monoGroup(dc.pieChart("#target-chart"), "target")) - ; - mv.charts.wpn = pie(monoGroup(dc.pieChart("#wpn-chart"), "wpn")) - ; - mv.charts.def = pie(monoGroup(dc.pieChart("#def-chart"), "def")) - .label(function(d) { return defLevelVerbose(d.data.key); }) - .title(function(d) { return defLevelVerbose(d.data.key) + ": " + d.value; }) - .colorAccessor(function(d) { return d.data.key; }) - .colorCalculator(function(k) { switch(k) { - case 0: return "#fd350d"; - case 1: return "#fdae6b"; - case 2: return "#6baed6"; - default: throw "Definition chart: Color access key out of range!"; - }}) - .filter(2) - ; - mv.charts.map = monoGroup(margined(wide(dc.bubbleChart("#map-chart"))), "map") - .height(500) - .colorCalculator(d3.scale.category20c()) - /* X */ - .keyAccessor(function(d) { return d.value.e + 1; }) - /* Y */ - .valueAccessor(function(d) { return d.value.j + 1; }) - /* R */ - .radiusValueAccessor(function(d) { return Math.sqrt(d.value.r); }) - .maxBubbleRelativeSize(0.045) - .x(d3.scale.log().domain([1, 100000])) - .y(d3.scale.log().domain([1, 300000])) - .axisPixelPadding({left:5, top: 10, right: 15, bottom: 5}) - .elasticX(true) - .elasticY(true) - .renderHorizontalGridLines(true) - .renderVerticalGridLines(true) - .title(function(d) { return "Map " + d.key + ":" + d.value.r; }) - .renderTitle(true) - ; - mv.charts.stats = trellisChart("#stat-chart", ["str", "agi", "vit", "dex", "int", "luk"].map(function(d) { mv.heap[d].name = d; return mv.heap[d]; })); - dc.renderlet(function() { mv.charts.stats(); }); - dc.renderAll(); - } - charter.filters = function() { - var r = {}, f; - for (var k in mv.charts) { - f = mv.charts[k].filter(); - if (f != null) { - r[k] = f; - } - } - return r; - } - function defLevelVerbose(level) { - switch (level) { - case 0: return "Undefined"; - case 1: return "Mixed"; - case 2: return "Defined"; - default: console.log(d, d.data); throw "Unknown definedness case (" + d.data.key + "); this shouldn't happen"; - } - } - return charter; - }(); - function wide(chart) { - return chart - .width(wideWidth) - ; - } - function med(chart) { - return chart - .width(medWidth) - ; - } - function thin(chart) { - return chart - .width(thinWidth) - ; - } - function short(chart) { - return chart - .height(130) - ; - } - function margined(chart) { - return chart - .margins({left: 60, right: 18, top: 5, bottom: 30}) - } - function monoGroup(chart, name) { - return chart - .dimension(mv.heap[name].dim) - .group(mv.heap[name].group) - .transitionDuration(500) - ; - } - function bar(chart) { - return margined(short(chart)) - .elasticY(true) - .gap(1) - .renderHorizontalGridLines(true) - .title(function(d) { return d.key + ": " + d.value; }) - .brushOn(true) - ; - } - function pie(chart) { - return thin(chart) - .radius(90) - .colorCalculator(d3.scale.category20c()) - ; - } - return mv; -}(mv || {}); diff --git a/js/mv/connect.js b/js/mv/connect.js deleted file mode 100644 index aba4c33..0000000 --- a/js/mv/connect.js +++ /dev/null @@ -1,156 +0,0 @@ -var mv = function(mv) { - mv.socket = { - connect: connect - }; - /* If we're updating due to something we received, then don't broadcast back. */ - var netrendering = false; - /* Our ID */ - var id = 0; - /* List of all users */ - var users = {}; - /* The user status box */ - var usersStatus = d3.select("#users-status"); - /* io.socket's socket */ - var socket; - function connect() { - socket = io.connect('http://localhost:3000'); - socket.on("connect", function() { console.log("CONNECT", arguments); }); - socket.on("disconnect", function() { console.log("DISCONNECT", arguments); }); - socket.emit('login'); - /* - * Protocol: - * selflogin -> id (I) - * login -> id (I), nick (S) - * nickset -> id (I), nick (S) - * users -> { id -> {nick (S), filters ({dim (S) -> filter (*)}) } } - * logout -> id (I) - */ - socket.on('selflogin', function(d) { - /* Acknowledged that we logged in */ - /* Take note of our ID. */ - id = d.id; - }); - socket.on('login', function(d) { - /* Someone else logging in */ - users[d.id] = { nick: d.nick, filters: {} }; - updateUsersStatus(); - }); - socket.on('users', function(d) { - /* We've got a list of all users. */ - /* The server is always right. */ - users = d.users; - updateUsersStatus(); - }); - socket.on('nickset', function(d) { - /* Someone, possibly us, changed their nick. */ - users[d.id].nick = d.nick; - updateUsersStatus(); - }); - socket.on('filterset', function(d) { - /* Someone changed their filter */ - users[d.id].filters = d.filters; - /* Use the variable netrendering to denote that we're rendering due to a change received from the network. */ - netrendering = true; - setOwnFilters(d.filters); - netrendering = false; - }); - socket.on('logout', function(d) { - /* Someone disconnected, take them off the list. */ - delete users[d.id]; - updateUsersStatus(); - }); - dc.renderlet(function() { - /* Hook a listener into dc's rendering routine. If it rerenders, broadcast the change. */ - if (netrendering) { - /* If we rendered due a change we received, don't broadcast it again. That would be A Bad Thing. */ - return; - } - socket.emit("filter", { filters: mv.charter.filters() }); - }); - } - function setOwnFilters(filters) { - /* See if there's any difference - if there isn't, don't update. */ - var change = false; - var key; - /* Check for keys in the filters to apply which are not in our charts. */ - for (key in filters) { - if (!(key in mv.charts)) - continue; - var filter = mv.charts[key].filter(); - if (typeof(filter) == "array") { - /* Crossfilter uses arrays to filter ranges. Exactly the first two elements are significant. */ - if (filter[0] == filters[key][0] && - filter[1] == filters[key][1]) { - continue; - } - } else if (filter == filters[key]) { - continue; - } - /* This filter differs. Apply it. */ - change = true; - mv.charts[key].filter(filters[key]); - } - /* Check for keys in our charts which are not in the filters to apply. */ - for (key in mv.charts) { - if (mv.charts[key].filter() != null) { - if (key in filters) { - /* This has already been handled above */ - continue; - } - /* There is no longer a filter applying here, clear it. */ - change = true; - mv.charts[key].filterAll(); - } - } - if (change) { - dc.redrawAll(); - } - } - function updateUsersStatus() { - /* Convert the user list to a form suitable for a d3 selection */ - var data = []; - for (var uid in users) { users[uid].id = uid; data.push(users[uid]); } - /* Data join */ - var userlist = usersStatus.selectAll(".user") - .data(data, function(d) { return d.id; }); - /* Enter */ - userlist - .enter().append("li").attr("class", "user") - ; - /* Update */ - userlist - .each(function(d,i) { - var elm = d3.select(this); - console.log("Userlist para appending", d,i); - var name = elm.select(".name"); - var nick = d.nick == null ? "Anonymous User " + d.id : d.nick; - if (d.id == id) { - /* This is us! We can edit our name. */ - if (name.empty()) { - console.log("Found our entry. id:", id, "datum", d); - name = elm.append("input").attr("class", "name") - .attr("type", "text") - .attr("placeholder", "Enter name here") - .on("change", function () { - /* A d3 select must be dereferenced twice to access the DOM entity */ - socket.emit("nick", { nick: name[0][0].value }); - }) - ; - } - name.attr("value", nick); - } else { - /* This is someone else. We can't edit their name. */ - if (name.empty()) { - name = elm.append("p").attr("class", "name"); - } - name.text(nick); - } - }) - ; - /* Remove */ - userlist - .exit().remove() - ; - } - return mv; -}(mv || {}); diff --git a/js/mv/heap.js b/js/mv/heap.js deleted file mode 100644 index 03f3c00..0000000 --- a/js/mv/heap.js +++ /dev/null @@ -1,63 +0,0 @@ -var mv = function(mv) { - mv.heap = function() { - var heap = {}; - var monoGroups = {}; - var statGran = 10; - heap.init = function() { - function ea(p, d) { p.e += d.e; p.j += d.j; p.r++; return p; } - function es(p, d) { p.e -= d.e; p.j -= d.j; p.r--; return p; } - function ez(p, d) { return { e: 0, j: 0, r: 0 }; } - heap.cfdata = crossfilter(mv.parser.records); - heap.all = heap.cfdata.groupAll().reduce(ea, es, ez); - monoGroup("date", function(d) { return d3.time.hour.round(d.date); }); - monoGroup("pc", function(d) { return d.pc; }); - monoGroup("map", function(d) { return d.map; }).reduce(ea, es, ez); - monoGroup("blvl", function(d) { return d.pcstat ? d.pcstat.blvl : 0; }); - monoGroup("type", function(d) { return d.type; }); - monoGroup("target", function(d) { return d.target; }); - monoGroup("wpn", function(d) { return d.wpn; }); - function sa(p, d) { - if (!d.pcstat) return p; - p.str[d.pcstat.str]++ || (p.str[d.pcstat.str] = 1); - p.agi[d.pcstat.agi]++ || (p.agi[d.pcstat.agi] = 1); - p.vit[d.pcstat.vit]++ || (p.vit[d.pcstat.vit] = 1); - p.dex[d.pcstat.dex]++ || (p.dex[d.pcstat.dex] = 1); - p.int[d.pcstat.int]++ || (p.int[d.pcstat.int] = 1); - p.luk[d.pcstat.luk]++ || (p.luk[d.pcstat.luk] = 1); - return p; - } - function ss(p, d) { - if (!d.pcstat) return p; - --p.str[d.pcstat.str] || (p.str[d.pcstat.str] = undefined); - --p.agi[d.pcstat.agi] || (p.agi[d.pcstat.agi] = undefined); - --p.vit[d.pcstat.vit] || (p.vit[d.pcstat.vit] = undefined); - --p.dex[d.pcstat.dex] || (p.dex[d.pcstat.dex] = undefined); - --p.int[d.pcstat.int] || (p.int[d.pcstat.int] = undefined); - --p.luk[d.pcstat.luk] || (p.luk[d.pcstat.luk] = undefined); - return p; - } - function sz(p, d) { return { str: [], agi: [], vit: [], dex: [], int: [], luk: [] }; } - monoGroup("str", function(d) { return d.pcstat ? d.pcstat.str : 0; }).reduce(sa, ss, sz); - monoGroup("agi", function(d) { return d.pcstat ? d.pcstat.agi : 0; }).reduce(sa, ss, sz); - monoGroup("vit", function(d) { return d.pcstat ? d.pcstat.vit : 0; }).reduce(sa, ss, sz); - monoGroup("dex", function(d) { return d.pcstat ? d.pcstat.dex : 0; }).reduce(sa, ss, sz); - monoGroup("int", function(d) { return d.pcstat ? d.pcstat.int : 0; }).reduce(sa, ss, sz); - monoGroup("luk", function(d) { return d.pcstat ? d.pcstat.luk : 0; }).reduce(sa, ss, sz); - /* Debugging group */ - /* - * How well defined a record is. - * 0 -> Record contains undefined data - * 1 -> Record is defined, but undefined records follow and may impede validity of findings - * 2 -> Record and all succeeding records are well defined - */ - monoGroup("def", function(d) { if (d.pcstat == undefined) { return 0; } if (d.date <= mv.parser.fullyDefinedCutoff()) { return 1; } return 2; }); - heap.def.dim.filterExact(2); - } - function monoGroup(name, mapping) { - heap[name] = {}; - return heap[name].group = (heap[name].dim = heap.cfdata.dimension(mapping)).group(); - } - return heap; - }(); - return mv; -}(mv || {}); diff --git a/js/mv/load.js b/js/mv/load.js deleted file mode 100644 index 39dd391..0000000 --- a/js/mv/load.js +++ /dev/null @@ -1,60 +0,0 @@ -var mv = function(mv) { - mv.loader = function() { - /* Set up handlers for file selector */ - var numfiles = 0; - var filenames = []; - var curfile = 0; - var loader = {}; - loader.onbulkstart = function(fevt) {}; - loader.onloadstart = function(evt) {}; - loader.onprogress = function(evt) {}; - loader.onabort = function(evt) { - alert('File load aborted!'); - }; - loader.onerror = function(evt) { - switch(evt.target.error.code) { - case evt.target.error.NOT_FOUND_ERR: - alert('File Not Found!'); - break; - case evt.target.error.NOT_READABLE_ERR: - alert('File is not readable'); - break; - case evt.target.error.ABORT_ERR: - break; // noop - default: - alert('An error occurred reading this file.'); - }; - }; - loader.numfiles = function() { return numfiles; }; - loader.filenames = function() { return filenames; }; - loader.curfile = function() { return curfile; }; - loader.init = function(each, after) { - document.getElementById('input').addEventListener('change', function(fevt) { - numfiles = fevt.target.files.length; - filenames = Array.prototype.map.call(fevt.target.files, function(d) { return d.name; }); - curfile = 0; - var reader = new FileReader(); - loader.onbulkstart(fevt); - reader.onerror = function() { loader.onerror.apply(null, arguments) }; - reader.onprogress = function() { loader.onprogress.apply(null, arguments) }; - reader.onabort = function() { loader.onabort.apply(null, arguments) }; - reader.onloadstart = function() { loader.onloadstart.apply(null, arguments) }; - reader.onload = function(evt) { - each(reader.result); - ++curfile; - if (curfile == numfiles) { - after(); - } else { - nextFile(); - } - }; - function nextFile() { - reader.readAsBinaryString(fevt.target.files[curfile]); - } - nextFile(); - }, false); - }; - return loader; - }(); - return mv; -}(mv || {}); diff --git a/js/mv/main.js b/js/mv/main.js deleted file mode 100644 index b7fcb1e..0000000 --- a/js/mv/main.js +++ /dev/null @@ -1,59 +0,0 @@ -var mv = function(mv) { - mv.init = function() { - console.log("Initialising"); - var loadbar = progress('loadbar'); - var filesbar = progress('filesbar'); - var lbase = loadbar.label; - loadbar.label = function() { - return lbase() == '100%' ? "Loaded '" + mv.loader.filenames()[mv.loader.curfile()]+ "' - Done!" : "Loading '" + mv.loader.filenames()[mv.loader.curfile()] + "' - " + lbase(); - }; - var fbase = filesbar.label; - filesbar.label = function () { - return fbase() == '100%' ? "Loaded " + mv.loader.numfiles() + " file(s) - Done!" : "Loading file " + mv.loader.curfile() + " of " + mv.loader.numfiles() + " - " + fbase(); - } - mv.loader.onbulkstart = function(fevt) { - loadbar.show(); - filesbar.show(); - }; - mv.loader.onloadstart = function(evt) { - filesbar.update(mv.loader.curfile(), mv.loader.numfiles()); - loadbar.reset(); - }; - mv.loader.onprogress = function(evt) { - if (evt.lengthComputable) { - loadbar.update(evt.loaded, evt.total); - } - }; - mv.loader.init(handleFile, postLoading); - function handleFile(data, curFileNum, numFiles) { - loadbar.complete(); - mv.parser.parseRecords(data); - } - function postLoading() { - filesbar.complete(); - /* TODO: This is still a total mess that doesn't really transition properly */ - setTimeout(function() { - loadbar.hide(); - }, 2000); - mv.heap.init(); - setTimeout(function() { - filesbar.hide(); - d3.select("#mask") - .transition() - .style("opacity", 0) - .remove(); - d3.selectAll(".vis-hide") - .style("display", "inline") - .transition() - .style("opacity", 1) - ; - mv.charter.init(); - console.log(document.getElementById("connect-option").checked); - if (document.getElementById("connect-option").checked) { - mv.socket.connect(); - } - }, 2000); - } - }; - return mv; -}(mv || {}); diff --git a/js/mv/parse.js b/js/mv/parse.js deleted file mode 100644 index 8094bc5..0000000 --- a/js/mv/parse.js +++ /dev/null @@ -1,109 +0,0 @@ -var mv = function(mv) { - mv.parser = function() { - var parser = {}; - var pcstat = {}; - var fullyDefinedCutoff = 0; - parser.records = []; - parser.fullyDefinedCutoff = function() { return fullyDefinedCutoff; }; - parser.parseRecords = function(data) { - var spl = data.split(/\r?\n/); - spl.forEach(function(e, i) { - var d; - d = e.match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); - if (d) { - var mapSID = parseInt(d[3]); - var ts = new Date(0); - ts.setUTCSeconds(d[1]); - var rec = { - date: ts, - pc: parseInt(d[2]), - map: map.nameByServerID(parseInt(d[3]), ts), - x: parseInt(d[4]), - y: parseInt(d[5]), - e: parseInt(d[6]), - j: parseInt(d[7]), - type: d[8], - pcstat: pcstat[d[2]], - target: "UNKNOWN", - dmg: -1010, - wpn: "UNKNOWN", - atktype: "UNKNOWN" - }; - if (pcstat[d[2]] == undefined && (!fullyDefinedCutoff || ts > fullyDefinedCutoff)) { - fullyDefinedCutoff = ts; - } - /* XXX: Fragile horrible and unstructured, this whole thing needs a rewrite really */ - if (i >= 2 && rec.type == "KILLXP") { - d = spl[i - 1].match(/^(\d+\.\d+) MOB(\d+) DEAD/); - if (d) { - var mID = parseInt(d[2]); - /* There's a massive wealth of data that can be collected from this. Number of assailants, weapons used, the relationships with the assailants... this can't be done with a simple lookbehind. For now, just extract what mob it was, and what the killing weapon used was. */ - d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) WPNDMG MOB(\d+) (\d+) FOR (\d+) WPN (\d+)/); - if (d) { - softAssert(mID == parseInt(d[6]), "Integrity error: MOB ID mismatch!"); - // softAssert(rec.pc == parseInt(d[2]), "Integrity error: PC ID mismatch!"); - rec.target = mob.nameByServerID(d[7]); - softAssert(rec.target, "Unknown target!") - rec.dmg = parseInt(d[8]); - rec.wpn = item.nameByServerID(d[9]); - rec.atktype = "Physical"; - } else { - /* Not weapon damage, perhaps it was spell damage? */ - d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) SPELLDMG MOB(\d+) (\d+) FOR (\d+) BY ([^ ]+)/); - if (d) { - rec.target = mob.nameByServerID(d[7]); - rec.dmg = parseInt(d[8]); - rec.wpn = d[9]; - rec.atktype = "Magical"; - } -// console.error("No match (deathblow):", spl[i - 2]); - } - } else { - d = spl[i - 1].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); - if (d) { - var clone = parser.records[parser.records.length - 1]; - softAssert(rec.map == clone.map, "Integrity error: MAP ID mismatch!"); - rec.target = clone.target; - softAssert(rec.target, "Unknown (cloned) target!"); - rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */ - rec.wpn = clone.wpn; - rec.atktype = clone.atktype; /* FIXME: Take into account what the assists used */ - } else { -// console.error("No match (clone):", spl[i - 1]); - } - } - } - parser.records.push(rec); - return; - } - d = e.match(/^(?:\d+\.\d+) PC(\d+) (?:\d+):(?:\d+),(?:\d+) STAT (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) /); - if (d) { - var s = { - str: parseInt(d[2]), - agi: parseInt(d[3]), - vit: parseInt(d[4]), - int: parseInt(d[5]), - dex: parseInt(d[6]), - luk: parseInt(d[7]) - }; - s.blvl = stat.minLevelForStats(s.str, s.agi, s.vit, s.int, s.dex, s.luk); - s.str = Math.floor(s.str / 10); - s.agi = Math.floor(s.agi / 10); - s.vit = Math.floor(s.vit / 10); - s.int = Math.floor(s.int / 10); - s.dex = Math.floor(s.dex / 10); - s.luk = Math.floor(s.luk / 10); - pcstat[d[1]] = s; - return; - } - }); - }; - function softAssert(expr, msg) { - if (!expr) { - console.error("SOFTASSERT FAILURE: " + msg); - } - } - return parser; - }(); - return mv; -}(mv || {}); diff --git a/js/util/memoize.js b/js/util/memoize.js deleted file mode 100644 index 796c060..0000000 --- a/js/util/memoize.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Helper function for memoization - * - * The passed labeller function must generate unique and exact names for entries that are naturally equal. - * IDs are persistent. - * - * names, idByName, and entries are accessible fields from the memoizer. - * Behaviour on external modification of these fields is undefined. - * - * An entry object passed to the memoizer has its field "id" set to the memoized id by default. - * The name of the ID field can be modified by calling the getter/setter idField() from the memoizer. - * Behaviour on changing the ID field used while the memoizer has already performed memoization is undefined. - */ -function memoize(labeller) { - var idField = "id"; - var names = []; - var idByName = {}; - var entries = []; - var _memoizer = function(entry) { - var name = labeller(entry); - if (name in idByName) { - /* We already have a suitable entry. */ - var id = idByName[name]; - return entries[id]; - } - /* New entry. */ - /* Set the entry's ID. */ - entry[idField] = entries.length; - /* Index the new entry. */ - idByName[name] = entry[idField]; - names[entry[idField]] = name; - entries.push(entry); - - return entry; - } - _memoizer.names = function() { return names; }; - _memoizer.idByName = function() { return idByName; }; - _memoizer.entries = function() { return entries; }; - _memoizer.idField = function(x) { - if (!arguments.length) return idField; - idField = x; - return _memoizer; - }; - return _memoizer; -} diff --git a/js/util/progress.js b/js/util/progress.js deleted file mode 100644 index 060e6dc..0000000 --- a/js/util/progress.js +++ /dev/null @@ -1,37 +0,0 @@ -function progress(root) { - var _progress = {}; - var container = document.getElementById(root); - var _percent = '0%'; - var bar = document.querySelector('#' + root + ' .percent'); - _progress.label = function() { - return _percent; - } - /* Updates the progress bar to display a specific percentage. No range checking performed. */ - _progress.setPercent = function(percent) { - _percent = percent; - bar.style.width = _percent; - bar.textContent = _progress.label(); - } - /* Updates the progress bar to display a percentage based on the current proportion of items done. */ - _progress.update = function(current, total) { - var percentLoaded = Math.min(100, Math.round((current / total) * 100)); - _progress.setPercent(percentLoaded + '%'); - }; - /* Resets the progress bar to display nothing done. */ - _progress.reset = function() { - _progress.setPercent('0%'); - } - /* Resets the progress bar to display everything done. */ - _progress.complete = function() { - _progress.setPercent('100%'); - } - /* Shows the progress bar. */ - _progress.show = function() { - container.className += ' loading'; - } - /* Hides the progress bar */ - _progress.hide = function() { - container.className = container.className.replace(/\bloading\b/, ''); - } - return _progress; -} diff --git a/js/util/trellis-chart.js b/js/util/trellis-chart.js deleted file mode 100644 index 65e07a2..0000000 --- a/js/util/trellis-chart.js +++ /dev/null @@ -1,140 +0,0 @@ -function trellisChart(anchor, monoGroups) { - /* attr -> {dim, group} key -> str amount, value -> { str, agi, vit, dex, int, luk } */ - - var attrs = monoGroups.map(function(d) { return d.name; }); - var attrsIdByName = {}; - monoGroups.forEach(function(d, i) { attrsIdByName[d.name] = i; }); - - var cellWidth = 5; - var radius = cellWidth / 2; - var subChartLength = 57; - var subChartUnpaddedLength = 50; - var subChartPadding = 7; - var filler = d3.scale.log().domain([1, 2]).range([0, 255]); - - var margin = {top: 10, right: 10, bottom: 20, left: 10}; - var anchor = d3.select(anchor); - var g = anchor.select("g"); - - var _chart = function() { - if (g.empty()) { - /* Make stuff! */ - var svg = anchor.append("svg"); - g = svg - .append("g"); - attrs.forEach(function(d, i) { - g - .append("text") - .attr("transform", function(d) { return "translate(0," + ((attrs.length - i) * subChartLength + 10 - subChartLength / 2) + ")"; }) - .text(d) - ; - g - .append("text") - .attr("transform", function(d) { return "translate(" + (i * subChartLength + 25 + 22) + "," + (attrs.length * subChartLength + 18) + ")"; }) - .text(d) - ; - }) - g = svg - .append("g") - .attr("transform", "translate(" + (margin.left + 25) + "," + (margin.top) + ")"); - } - /* Group first into columns for each stat. We have one column for each of the stat monoGroups. */ - /* - * monoGroups is an array of each stat dimension. We can consider each column to have data in the following format: - * { group: function, dim: function, name: stat } - */ - var columns = g.selectAll(".column") - .data(monoGroups); - var colE = columns - .enter().append("g") - .attr("class", "column") - .attr("transform", function(d) { return "translate(" + (attrsIdByName[d.name] * subChartLength) + ",0)"; }) - ; - colE - .append("line") - .attr("x1", -cellWidth) - .attr("x2", -cellWidth) - .attr("y1", -cellWidth) - .attr("y2", subChartLength * attrs.length - subChartPadding) - .attr("class", "border-line") - ; - colE - .append("line") - .attr("x1", subChartUnpaddedLength) - .attr("x2", subChartUnpaddedLength) - .attr("y1", -cellWidth) - .attr("y2", subChartLength * attrs.length - subChartPadding) - .attr("class", "border-line") - ; - /* Each stat has an array for its value. Group these to find the x position. */ - /* - * The function transforms the data to take the grouping. We can consider each x position grouping to have data in the following format: - * { key: position, value: [{[stat] -> [y pos] -> count}] } - */ - var colposg = columns.selectAll(".colpos") - .data(function(d, i) { -// console.log("Incoming colposg format:", d, i, "Transformed to:", d.group.all().map(function(d2) { d2.name = d.name; return d2; })); - return d.group.all().map(function(d2) { d2.name = d.name; return d2; }); - }, function(d) { return d.key; }); - colposg - .enter().append("g") - .attr("class", "colpos") - .attr("transform", function(d) { return "translate(" + (d.key * cellWidth) + ",0)"; }) - ; - /* Next, split up each x position grouping into its y stat grouping. */ - /* - * We can consider each y stat grouping to have data in the following format: - * v[y pos] -> count; v.name -> name - */ - var rows = colposg.selectAll(".row") - .data(function(d, i) { -// console.log("Incoming row format:", d, i, "Transformed to:", attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; })); - return attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; }); - }); - rows - .enter().append("g") - .attr("class", "row") - .attr("transform", function(d) { return "translate(0," + ((attrs.length - attrsIdByName[d.name] - 1) * subChartLength) + ")"; }) - ; - /* Finally, split up each y stat grouping into x. */ - var vmax = 0; - var cells = rows.selectAll(".cell") - .data(function(d, i) { -// console.log("Incoming cells format:", d, i, "Transformed to:", d.data); - return d.data; - }); - cells - .enter().append("circle") - .attr("class", "cell") - .attr("r", radius) - ; - cells - .each(function(d) { - if (d > vmax) vmax = d; - }) - ; - filler.domain([1, vmax + 1]); - cells - .attr("fill", function(d) { - return d ? d3.rgb(255 - filler(d + 1), 255 - filler(d + 1) / 2, 255 - filler(d + 1) / 3) : "white"; - }) - .attr("transform", function(d, i) { return "translate(0," + ((10-i) * cellWidth) + ")"; }) - ; - cells - .exit() - .remove() - ; - } - - _chart.filter = function() { - /* - * TODO: - * This is going to be interesting. As the chart is not charting a single - * monogroup, and most code is built around this assumption, this might - * well end up being a messy special case. - */ - return null; - } - - return _chart; -} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..2690b1f --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,134 @@ +/* Ensure sane defaults */ + +body { + padding: 0; + margin: 0; +} + +/* Top-level layout */ +.side { + float: right; +} +#main { + overflow: hidden; +} + +#mask { + position: absolute; + width: 100%; + height: 100%; + background-color: #333; + opacity: .8; + -moz-transition: opacity 1s linear; + -o-transition: opacity 1s linear; + -webkit-transition: opacity 1s linear; + transition: opacity 1s linear; + top: 0; +} + +body, #main, #status, .side { + min-height: 40px; +} + +body { + min-width: 1350px; +} + +#status { + width: 100%; + font-size: smaller; +} + +/* Columns */ + +.med { + width: 400px; +} + +.thin { + width: 250px; +} + +#main, .side { + font-size: smaller; +} + +/* Titles */ +h3 { + margin: 0.3em 0.8em; +} + +/* Loadinfo panel */ + +#loadinfo { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + width: 647px; + height: 400px; + border: 1px grey solid; + margin: auto; + background-color: #fff; + padding: 20px; +} + +/* Hide charts while loadinfo is shown */ + +.vis-hide { + display: none; + opacity: 0; +} + +/* Loading bars */ +.progressbar { + margin: 10px 0; + padding: 3px; + border: 1px solid #000; + font-size: 14px; + clear: both; + opacity: 0; +} + +.progressbar.loading { + opacity: 1.0; +} + +.progressbar .percent { + background-color: #99ccff; + height: auto; + width: 0; + white-space: nowrap; +} + +/* Stat chart */ + +#stat-chart svg g g.column .border-line { + fill: none; + stroke: #ccc; + opacity: .5; + shape-rendering: crispEdges; +} + +/* User list */ + +#users-status { + padding: 0; +} + +#users-status li.user { + list-style: none; +} + +/* Utility */ +.fader { + -moz-transition: opacity 1s linear; + -o-transition: opacity 1s linear; + -webkit-transition: opacity 1s linear; + transition: opacity 1s linear; +} + +.help { + cursor: help; +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..baebbb2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,96 @@ + + +Manavis + + + + +
    +
    +
    +

    Users

    +
      +
      +
      +

      Instance breakdown by Character Base Level

      +
      +
      +

      Instance breakdown by Stat allocation

      +
      +
      +
      +
      +
      +
      +

      Instance breakdown by Target

      +
      +
      +

      Instance breakdown by Weapon

      +
      +
      +

      Instance breakdown by Type

      +
      +
      +

      Definedness of records [?]

      +
      +
      +
      +
      + +
      +
      +

      Breakdown by Map [?]

      +
      +
      +

      Instance breakdown by Date

      +
      +
      +

      Instance breakdown by Character ID [?]

      +
      +
      +
      +
      +
      +

      Manavis

      + + +

      Select records to load and display

      +

      You can load any number of files at once.

      + + +
      +
      0%
      +
      +
      +
      0%
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/js/comp/item.js b/public/js/comp/item.js new file mode 100644 index 0000000..65596be --- /dev/null +++ b/public/js/comp/item.js @@ -0,0 +1,809 @@ +var item = function(){ + var item = {}; + var items = { + 0:"DEFAULT", + 501:"CactusDrink", + 502:"CactusPotion", + 503:"CasinoCoins", + 504:"DecorCandy", + 505:"MaggotSlime", + 506:"CandyCane", + 507:"ScorpionStinger", + 508:"XmasCake", + 509:"ChocolateBar", + 510:"Candy", + 511:"SantaHat", + 512:"GingerBreadMan", + 513:"Cake", + 514:"XmasCandyCane", + 515:"PurplePresentBox", + 516:"BluePresentBox", + 517:"RedScorpionStinger", + 518:"BugLeg", + 519:"CherryCake", + 520:"EasterEgg", + 521:"Dagger", + 522:"SharpKnife", + 523:"LeatherShirt", + 524:"FancyHat", + 525:"MinersHat", + 526:"CoinBag", + 527:"Milk", + 528:"Boots", + 529:"IronArrow", + 530:"ShortBow", + 531:"MinerGloves", + 532:"LeatherGloves", + 533:"RoastedMaggot", + 534:"OrangeCupcake", + 535:"RedApple", + 536:"ShortSword", + 537:"TreasureKey", + 538:"GreenPresentBox", + 539:"Beer", + 540:"EmptyBottle", + 541:"BottleOfWater", + 542:"BottleOfSand", + 543:"StandardHeadband", + 544:"SilkHeadband", + 545:"ForestBow", + 546:"DesertShirt", + 547:"Bardiche", + 548:"Halberd", + 549:"Axe", + 550:"BlacksmithsAxe", + 551:"AquaHint", + 552:"MagentaHint", + 553:"YellowHint", + 554:"GreenHint", + 555:"TealHint", + 556:"PurpleHint", + 557:"RedHint", + 558:"BlueHint", + 559:"OrangeHint", + 560:"GrayHint", + 561:"Sabre", + 562:"ChickenLeg", + 563:"WinterGloves", + 564:"TurtleneckSweater", + 565:"PinkPetal", + 566:"SmallMushroom", + 567:"IronPotion", + 568:"ConcentrationPotion", + 569:"RawLog", + 570:"BoneKnife", + 571:"Setzer", + 572:"Scimitar", + 573:"Falchion", + 574:"Scorpion", + 575:"DesertBow", + 576:"Beheader", + 577:"BoneDarts", + 578:"SandCutter", + 579:"RockKnife", + 580:"StaffOfLife", + 581:"CrescentRod", + 582:"StaffOfFire", + 583:"StaffOfIce", + 584:"Jackal", + 585:"ScarabArmlet", + 586:"CottonShorts", + 587:"Sword", + 594:"Spear", + 601:"SteelShield", + 602:"WoodenShield", + 603:"LeatherShield", + 610:"JeansShorts", + 611:"WhiteFur", + 612:"CaveSnakeLamp", + 613:"HardSpike", + 614:"PinkAntenna", + 615:"PumpkinHelmet", + 616:"AxeHat", + 617:"PirateHat", + 618:"Goggles", + 619:"LeatherGoggles", + 620:"Circlet", + 621:"Eyepatch", + 622:"Bandana", + 623:"Scythe", + 624:"VNeckSweater", + 625:"ChainmailShirt", + 626:"LightPlatemail", + 627:"TopHat", + 628:"FunkyHat", + 629:"MushHat", + 630:"ShroomHat", + 631:"DarkCrystal", + 632:"CottonSkirt", + 633:"ChristmasElfHat", + 634:"FaceMask", + 635:"SantaCookie", + 636:"WarlordHelmet", + 637:"KnightsHelmet", + 638:"InfantryHelmet", + 639:"CrusadeHelmet", + 640:"IronOre", + 641:"SnakeSkin", + 642:"JeansChaps", + 643:"WhiteCowboyHat", + 644:"BlackCowboyHat", + 645:"GoldenPlatemail", + 646:"Crown", + 647:"DevelopersCap", + 648:"CottonTrousers", + 649:"WhiteEvokersRobeBlue", + 650:"BlackEvokersRobeBlue", + 651:"WhiteWizardRobe", + 652:"BlackWizardRobe", + 653:"ApprenticeRobe", + 654:"Cap", + 655:"FurBoots", + 656:"SerfHat", + 657:"Orange", + 658:"WarlordPlate", + 659:"GoldenWarlordPlate", + 660:"CottonCloth", + 661:"RedRose", + 662:"WhiteRose", + 663:"DarkRedRose", + 664:"PinkRose", + 665:"YellowRose", + 666:"BlackRose", + 667:"OrangeRose", + 668:"BlueRose", + 669:"YellowTulip", + 670:"PurpleTulip", + 671:"RedTulip", + 672:"WhiteTulip", + 673:"PinkTulip", + 674:"OrangeTulip", + 675:"GraduationCap", + 676:"Steak", + 677:"HeartNecklace", + 678:"NohMask", + 679:"DemonMask", + 680:"MauveHerb", + 681:"CobaltHerb", + 682:"GambogeHerb", + 683:"AlizarinHerb", + 684:"TinyHealingPotion", + 685:"SmallHealingPotion", + 686:"MediumHealingPotion", + 687:"LargeHealingPotion", + 688:"TankTop", + 689:"ShortTankTop", + 690:"RedDye", + 691:"GreenDye", + 692:"DarkBlueDye", + 693:"YellowDye", + 694:"LightBlueDye", + 695:"PinkDye", + 696:"BlackDye", + 697:"OrangeDye", + 698:"PurpleDye", + 699:"DarkGreenDye", + 700:"Pearl", + 701:"PileOfAsh", + 702:"WeddingRing", + 703:"SulphurPowder", + 704:"IronPowder", + 705:"ManaPotion", + 706:"GoldenScorpionStinger", + 707:"MonsterOilPotion", + 708:"LeatherPatch", + 709:"BlackScorpionStinger", + 710:"SnakeTongue", + 711:"MountainSnakeTongue", + 712:"GrassSnakeTongue", + 713:"CaveSnakeTongue", + 714:"SnakeEgg", + 715:"MountainSnakeEgg", + 716:"GrassSnakeEgg", + 717:"CaveSnakeEgg", + 718:"SilkCocoon", + 719:"GreenApple", + 720:"SilkRobe", + 721:"HighPriestCrown", + 722:"MonsterSkullHelmet", + 723:"DesertHat", + 724:"CottonHeadband", + 725:"GMCap", + 726:"GMRobe", + 727:"Iten", + 728:"MoubooFigurine", + 729:"WarpedLog", + 730:"Lifestone", + 731:"AssassinPants", + 732:"DruidTreeBranch", + 733:"PurificationPotion", + 734:"BlackBoots", + 735:"CottonBoots", + 736:"WhiteCake", + 737:"ChocolateCake", + 738:"OrangeCake", + 739:"AppleCake", + 740:"Root", + 741:"CottonGloves", + 742:"FourLeafClover", + 743:"Acorn", + 744:"DilutedConcentrationPotion", + 745:"DarkConcentrationPotion", + 746:"MopoxCurePotion", + 747:"LacedChocolateCake", + 748:"LacedOrangeCupcake", + 749:"Towel", + 750:"SlowPoisonPotion", + 751:"PinkieHat", + 752:"FluffyHat", + 753:"BatWing", + 754:"BatTeeth", + 755:"AssassinShirt", + 756:"AssassinGloves", + 757:"AssassinBoots", + 758:"WoodenStaff", + 762:"TerraniteArrow", + 763:"TerraniteOre", + 766:"TerraniteHelmet", + 767:"TerraniteChestArmor", + 768:"TerraniteLegs", + 769:"GuyFawkesMask", + 770:"FairyHat", + 771:"Miniskirt", + 772:"WispPowder", + 773:"SpectrePowder", + 774:"PoltergeistPowder", + 775:"Bone", + 776:"Skull", + 777:"RottenRags", + 778:"DiseasedHeart", + 779:"UndeadEar", + 780:"UndeadEye", + 782:"ForestArmor", + 783:"PlatynaRedDress", + 784:"ZombieNachos", + 785:"LadyFingers", + 786:"JellAhh", + 787:"Snapple", + 788:"BeetleJuice", + 789:"GutBuster", + 790:"BloodWine", + 791:"YetiSkinShirt", + 792:"BromenalBoots", + 793:"BromenalChest", + 794:"BromenalGloves", + 795:"BromenalHelmet", + 796:"BromenalLegs", + 797:"BromenalShield", + 798:"SorcererRobeRed", + 799:"MylarinDust", + 800:"BowlerHatBrown", + 801:"PinkieHelmet", + 802:"EasterBasket", + 803:"GrassLiner", + 804:"JellyBeans", + 805:"ChocolateMouboo", + 806:"ReedBundle", + 807:"GrassSeed", + 808:"HitchhikersTowel", + 809:"WhiteHitchhikersTowel", + 810:"RedHitchhikersTowel", + 811:"GreenHitchhikersTowel", + 812:"BlueHitchhikersTowel", + 813:"YellowHitchhikersTowel", + 814:"PurpleHitchhikersTowel", + 815:"OrangeHitchhikersTowel", + 816:"PinkHitchhikersTowel", + 817:"TealHitchhikersTowel", + 818:"LimeHitchhikersTowel", + 819:"DiamondPowder", + 820:"RubyPowder", + 821:"EmeraldPowder", + 822:"SapphirePowder", + 823:"TopazPowder", + 824:"AmethystPowder", + 825:"TinyManaElixir", + 826:"SmallManaElixir", + 827:"MediumManaElixir", + 828:"LargeManaElixir", + 829:"CrozeniteFourLeafAmulet", + 830:"BromenalFourLeafAmulet", + 831:"SilverFourLeafAmulet", + 832:"GoldenFourLeafAmulet", + 833:"BrokenFourLeafAmulet", + 834:"BrokenDoll", + 835:"HyvernStinger", + 836:"GrubSlime", + 838:"CranberryLollipop", + 839:"GrapeLollipop", + 840:"OrangeLollipop", + 841:"RedDottedWrap", + 842:"YellowDottedWrap", + 843:"BlueDottedWrap", + 844:"PurpleStripedWrap", + 845:"RedGoldenStripedWrap", + 846:"GreenRedStripedWrap", + 847:"PlushMouboo", + 848:"Earmuffs", + 849:"OpenPresentBox", + 850:"ClosedChristmasBox", + 851:"StickReinboo", + 852:"LeatherBall", + 853:"Doll", + 854:"ElfNightcap", + 855:"Sunglasses", + 856:"KnitCap", + 857:"LeatherTrousers", + 858:"WolvernTooth", + 859:"WolvernPelt", + 860:"SquirrelPelt", + 861:"WhiteBellTuber", + 862:"IcedWater", + 863:"SilverMirror", + 864:"BookPage", + 865:"Grimoire", + 866:"LeatherSuitcase", + 867:"IceGladius", + 868:"SilkGloves", + 869:"Antlers", + 870:"FineDress", + 871:"SealedSoul", + 872:"LockPicks", + 873:"LazuriteShard", + 874:"LazuriteCrystal", + 875:"HeartOfLazurite", + 876:"WarlordBoots", + 877:"BullHelmet", + 878:"BansheeBow", + 879:"HeartOfIsis", + 880:"LazuriteRobe", + 881:"RaggedShorts", + 882:"RedEggshellHat", + 883:"BlueEggshellHat", + 884:"YellowEggshellHat", + 885:"GreenEggshellHat", + 886:"OrangeEggshellHat", + 887:"DarkEggshellHat", + 888:"MagicGMTopHat", + 889:"MurdererCrown", + 890:"BeanieCopter", + 1198:"JackOSoul", + 1199:"Arrow", + 1200:"Bow", + 1201:"Knife", + 1202:"CottonShirt", + 1203:"RangerHat", + 1204:"AntlerHat", + 1205:"ChristmasTreeHat", + 1206:"SantaBeardHat", + 1207:"RedChristmasStocking", + 1208:"RedEasterEgg", + 1209:"GreenEasterEgg", + 1210:"BlueEasterEgg", + 1211:"YellowEasterEgg", + 1212:"PinkEasterEgg", + 1213:"TealEasterEgg", + 1214:"BunnyEars", + 1215:"ToySabre", + 1216:"MoubooHead", + 1217:"CatEars", + 1218:"PaperBag", + 1219:"MoubootaurHead", + 1220:"BunchOfParsley", + 1221:"SkullMask", + 1228:"LightCrystal", + 1229:"CaramelApple", + 1230:"LollipopColor1", + 1231:"LollipopColor2", + 1232:"LollipopColor3", + 1233:"FakeFangs", + 1234:"RedOrnament", + 1235:"YellowOrnament", + 1236:"GreenOrnament", + 1237:"AquaOrnament", + 1238:"BlueOrnament", + 1239:"MagentaOrnament", + 1240:"SantaSnowGlobe", + 1241:"SnowmanSnowGlobe", + 1242:"SnowGoggles", + 1244:"DarkTalisman", + 1245:"BentNeedle", + 1246:"DarkEasterEgg", + 1247:"HeartGlasses", + 1248:"Blueberries", + 1249:"StrangeCoin", + 1250:"Pear", + 1251:"Plum", + 1252:"Cherry", + 1253:"GoldenDeliciousApple", + 1254:"DarkPetal", + 1255:"WhiteRabbitEars", + 1256:"EggshellHat", + 1257:"FlawedLens", + 1258:"Honey", + 1276:"OperaMask", + 1277:"JesterMask", + 1278:"WitchHat", + 1279:"GoblinMask", + 1280:"Scissors", + 1281:"ShockSweet", + 1282:"BoneArrows", + 2050:"RedCottonShirt", + 2051:"GreenCottonShirt", + 2052:"DarkBlueCottonShirt", + 2053:"YellowCottonShirt", + 2054:"LightBlueCottonShirt", + 2055:"PinkCottonShirt", + 2056:"BlackCottonShirt", + 2057:"OrangeCottonShirt", + 2058:"PurpleCottonShirt", + 2059:"DarkGreenCottonShirt", + 2060:"RedVNeckSweater", + 2061:"GreenVNeckSweater", + 2062:"DarkBlueVNeckSweater", + 2063:"YellowVNeckSweater", + 2064:"LightBlueVNeckSweater", + 2065:"PinkVNeckSweater", + 2066:"BlackVNeckSweater", + 2067:"OrangeVNeckSweater", + 2068:"PurpleVNeckSweater", + 2069:"DarkGreenVNeckSweater", + 2070:"RedTurtleneckSweater", + 2071:"GreenTurtleneckSweater", + 2072:"DarkBlueTurtleneckSweater", + 2073:"YellowTurtleneckSweater", + 2074:"LightBlueTurtleneckSweater", + 2075:"PinkTurtleneckSweater", + 2076:"BlackTurtleneckSweater", + 2077:"OrangeTurtleneckSweater", + 2078:"PurpleTurtleneckSweater", + 2079:"DarkGreenTurtleneckSweater", + 2080:"RedSilkRobe", + 2081:"GreenSilkRobe", + 2082:"DarkBlueSilkRobe", + 2083:"YellowSilkRobe", + 2084:"LightBlueSilkRobe", + 2085:"PinkSilkRobe", + 2086:"BlackSilkRobe", + 2087:"OrangeSilkRobe", + 2088:"PurpleSilkRobe", + 2089:"DarkGreenSilkRobe", + 2090:"RedTankTop", + 2091:"GreenTankTop", + 2092:"DarkBlueTankTop", + 2093:"YellowTankTop", + 2094:"LightBlueTankTop", + 2095:"PinkTankTop", + 2096:"BlackTankTop", + 2097:"OrangeTankTop", + 2098:"PurpleTankTop", + 2099:"DarkGreenTankTop", + 2100:"RedCottonSkirt", + 2101:"GreenCottonSkirt", + 2102:"DarkBlueCottonSkirt", + 2103:"YellowCottonSkirt", + 2104:"LightBlueCottonSkirt", + 2105:"PinkCottonSkirt", + 2106:"BlackCottonSkirt", + 2107:"OrangeCottonSkirt", + 2108:"PurpleCottonSkirt", + 2109:"DarkGreenCottonSkirt", + 2110:"RedCottonShorts", + 2111:"GreenCottonShorts", + 2112:"DarkBlueCottonShorts", + 2113:"YellowCottonShorts", + 2114:"LightBlueCottonShorts", + 2115:"PinkCottonShorts", + 2116:"BlackCottonShorts", + 2117:"OrangeCottonShorts", + 2118:"PurpleCottonShorts", + 2119:"DarkGreenCottonShorts", + 2120:"RedShortTankTop", + 2121:"GreenShortTankTop", + 2122:"DarkBlueShortTankTop", + 2123:"YellowShortTankTop", + 2124:"LightBlueShortTankTop", + 2125:"PinkShortTankTop", + 2126:"BlackShortTankTop", + 2127:"OrangeShortTankTop", + 2128:"PurpleShortTankTop", + 2129:"DarkGreenShortTankTop", + 2130:"RedDesertHat", + 2131:"GreenDesertHat", + 2132:"DarkBlueDesertHat", + 2133:"YellowDesertHat", + 2134:"LightBlueDesertHat", + 2135:"PinkDesertHat", + 2136:"BlackDesertHat", + 2137:"OrangeDesertHat", + 2138:"PurpleDesertHat", + 2139:"DarkGreenDesertHat", + 2140:"RedCottonHeadband", + 2141:"GreenCottonHeadband", + 2142:"DarkBlueCottonHeadband", + 2143:"YellowCottonHeadband", + 2144:"LightBlueCottonHeadband", + 2145:"PinkCottonHeadband", + 2146:"BlackCottonHeadband", + 2147:"OrangeCottonHeadband", + 2148:"PurpleCottonHeadband", + 2149:"DarkGreenCottonHeadband", + 2150:"RedCottonBoots", + 2151:"GreenCottonBoots", + 2152:"DarkBlueCottonBoots", + 2153:"YellowCottonBoots", + 2154:"LightBlueCottonBoots", + 2155:"PinkCottonBoots", + 2156:"BlackCottonBoots", + 2157:"OrangeCottonBoots", + 2158:"PurpleCottonBoots", + 2159:"DarkGreenCottonBoots", + 2160:"RedCottonGloves", + 2161:"GreenCottonGloves", + 2162:"DarkBlueCottonGloves", + 2163:"YellowCottonGloves", + 2164:"LightBlueCottonGloves", + 2165:"PinkCottonGloves", + 2166:"BlackCottonGloves", + 2167:"OrangeCottonGloves", + 2168:"PurpleCottonGloves", + 2169:"DarkGreenCottonGloves", + 2170:"RedMiniskirt", + 2171:"GreenMiniskirt", + 2172:"DarkBlueMiniskirt", + 2173:"YellowMiniskirt", + 2174:"LightBlueMiniskirt", + 2175:"PinkMiniskirt", + 2176:"BlackMiniskirt", + 2177:"OrangeMiniskirt", + 2178:"PurpleMiniskirt", + 2179:"DarkGreenMiniskirt", + 2180:"RedCottonTrousers", + 2181:"GreenCottonTrousers", + 2182:"DarkBlueCottonTrousers", + 2183:"YellowCottonTrousers", + 2184:"LightBlueCottonTrousers", + 2185:"PinkCottonTrousers", + 2186:"BlackCottonTrousers", + 2187:"OrangeCottonTrousers", + 2188:"PurpleCottonTrousers", + 2189:"DarkGreenCottonTrousers", + 2190:"RedRabbitEars", + 2191:"GreenRabbitEars", + 2192:"DarkBlueRabbitEars", + 2193:"YellowRabbitEars", + 2194:"LightBlueRabbitEars", + 2195:"PinkRabbitEars", + 2196:"BlackRabbitEars", + 2197:"OrangeRabbitEars", + 2198:"PurpleRabbitEars", + 2199:"DarkGreenRabbitEars", + 2200:"RedWizardHat", + 2201:"GreenWizardHat", + 2202:"DarkBlueWizardHat", + 2203:"YellowWizardHat", + 2204:"LightBlueWizardHat", + 2205:"PinkWizardHat", + 2206:"BlackWizardHat", + 2207:"OrangeWizardHat", + 2208:"PurpleWizardHat", + 2209:"DarkGreenWizardHat", + 2210:"RedBowlerHat", + 2211:"GreenBowlerHat", + 2212:"DarkBlueBowlerHat", + 2213:"YellowBowlerHat", + 2214:"LightBlueBowlerHat", + 2215:"PinkBowlerHat", + 2216:"BlackBowlerHat", + 2217:"OrangeBowlerHat", + 2218:"PurpleBowlerHat", + 2219:"DarkGreenBowlerHat", + 2220:"RedSorcererRobeRed", + 2221:"GreenSorcererRobeRed", + 2222:"DarkBlueSorcererRobeRed", + 2223:"YellowSorcererRobeRed", + 2224:"LightBlueSorcererRobeRed", + 2225:"PinkSorcererRobeRed", + 2226:"BlackSorcererRobeRed", + 2227:"OrangeSorcererRobeRed", + 2228:"PurpleSorcererRobeRed", + 2229:"DarkGreenSorcererRobeRed", + 2230:"RedBowlerHatBrown", + 2231:"GreenBowlerHatBrown", + 2232:"DarkBlueBowlerHatBrown", + 2233:"YellowBowlerHatBrown", + 2234:"LightBlueBowlerHatBrown", + 2235:"PinkBowlerHatBrown", + 2236:"BlackBowlerHatBrown", + 2237:"OrangeBowlerHatBrown", + 2238:"PurpleBowlerHatBrown", + 2239:"DarkGreenBowlerHatBrown", + 2240:"FineRedDress", + 2241:"FineGreenDress", + 2242:"FineDarkBlueDress", + 2243:"FineYellowDress", + 2244:"FineLightBlueDress", + 2245:"FinePinkDress", + 2246:"FineBlackDress", + 2247:"FineOrangeDress", + 2248:"FinePurpleDress", + 2249:"FineDarkGreenDress", + 2250:"RedCottonCloth", + 2251:"GreenCottonCloth", + 2252:"DarkBlueCottonCloth", + 2253:"YellowCottonCloth", + 2254:"LightBlueCottonCloth", + 2255:"PinkCottonCloth", + 2256:"BlackCottonCloth", + 2257:"OrangeCottonCloth", + 2258:"PurpleCottonCloth", + 2259:"DarkGreenCottonCloth", + 3000:"JackOLantern", + 3001:"RubberBat", + 3002:"RealisticBrain", + 3003:"JarofBlood", + 3004:"Tongue", + 3006:"TonoriDelight", + 3007:"Marshmallow", + 3009:"JellySkull", + 3010:"CandyPumpkin", + 3011:"PumpkinSeeds", + 4000:"AngryScorpionStinger", + 4001:"Coal", + 4002:"Diamond", + 4003:"Ruby", + 4004:"Emerald", + 4005:"Sapphire", + 4006:"Topaz", + 4007:"Amethyst", + 4008:"DiamondRing", + 4009:"RubyRing", + 4010:"EmeraldRing", + 4011:"SapphireRing", + 4012:"TopazRing", + 4013:"AmethystRing", + 4014:"SimpleRing", + 4015:"IronIngot", + 4016:"BanditHood", + 4017:"RedPowder", + 4018:"YellowPowder", + 4019:"BluePowder", + 4020:"CandleHelmet", + 4021:"YellowPresentBox", + 4022:"WhitePresentBox", + 4023:"AnimalBones", + 4024:"FrozenYetiTear", + 4025:"YetiClaw", + 4026:"IceCube", + 4027:"YetiMask", + 4028:"WizardHat", + 4029:"GrimaceOfDementia", + 4030:"BowlerHat", + 4031:"Monocle", + 4032:"PanHat", + 4033:"ChefHat", + 4034:"BlackPearl", + 4035:"PickledBeets", + 4036:"RoastedAcorn", + 4037:"WhiteBlanket", + 4038:"WhiteSaddleRug", + 4039:"RedSaddleRug", + 4040:"RawTalisman", + 4041:"FlightTalisman", + 4042:"RedNose", + 5000:"RedSorcererRobeGreen", + 5001:"GreenSorcererRobeGreen", + 5002:"DarkBlueSorcererRobeGreen", + 5003:"YellowSorcererRobeGreen", + 5004:"LightBlueSorcererRobeGreen", + 5005:"PinkSorcererRobeGreen", + 5006:"BlackSorcererRobeGreen", + 5007:"OrangeSorcererRobeGreen", + 5008:"PurpleSorcererRobeGreen", + 5009:"DarkGreenSorcererRobeGreen", + 5010:"SorcererRobeGreen", + 5011:"RedSorcererRobeDarkBlue", + 5012:"GreenSorcererRobeDarkBlue", + 5013:"DarkBlueSorcererRobeDarkBlue", + 5014:"YellowSorcererRobeDarkBlue", + 5015:"LightBlueSorcererRobeDarkBlue", + 5016:"PinkSorcererRobeDarkBlue", + 5017:"BlackSorcererRobeDarkBlue", + 5018:"OrangeSorcererRobeDarkBlue", + 5019:"PurpleSorcererRobeDarkBlue", + 5020:"DarkGreenSorcererRobeDarkBlue", + 5021:"SorcererRobeDarkBlue", + 5022:"RedSorcererRobeYellow", + 5023:"GreenSorcererRobeYellow", + 5024:"DarkBlueSorcererRobeYellow", + 5025:"YellowSorcererRobeYellow", + 5026:"LightBlueSorcererRobeYellow", + 5027:"PinkSorcererRobeYellow", + 5028:"BlackSorcererRobeYellow", + 5029:"OrangeSorcererRobeYellow", + 5030:"PurpleSorcererRobeYellow", + 5031:"DarkGreenSorcererRobeYellow", + 5032:"SorcererRobeYellow", + 5033:"RedSorcererRobeLightBlue", + 5034:"GreenSorcererRobeLightBlue", + 5035:"DarkBlueSorcererRobeLightBlue", + 5036:"YellowSorcererRobeLightBlue", + 5037:"LightBlueSorcererRobeLightBlue", + 5038:"PinkSorcererRobeLightBlue", + 5039:"BlackSorcererRobeLightBlue", + 5040:"OrangeSorcererRobeLightBlue", + 5041:"PurpleSorcererRobeLightBlue", + 5042:"DarkGreenSorcererRobeLightBlue", + 5043:"SorcererRobeLightBlue", + 5044:"RedSorcererRobePink", + 5045:"GreenSorcererRobePink", + 5046:"DarkBlueSorcererRobePink", + 5047:"YellowSorcererRobePink", + 5048:"LightBlueSorcererRobePink", + 5049:"PinkSorcererRobePink", + 5050:"BlackSorcererRobePink", + 5051:"OrangeSorcererRobePink", + 5052:"PurpleSorcererRobePink", + 5053:"DarkGreenSorcererRobePink", + 5054:"SorcererRobePink", + 5055:"RedSorcererRobeBlack", + 5056:"GreenSorcererRobeBlack", + 5057:"DarkBlueSorcererRobeBlack", + 5058:"YellowSorcererRobeBlack", + 5059:"LightBlueSorcererRobeBlack", + 5060:"PinkSorcererRobeBlack", + 5061:"BlackSorcererRobeBlack", + 5062:"OrangeSorcererRobeBlack", + 5063:"PurpleSorcererRobeBlack", + 5064:"DarkGreenSorcererRobeBlack", + 5065:"SorcererRobeBlack", + 5066:"RedSorcererRobeOrange", + 5067:"GreenSorcererRobeOrange", + 5068:"DarkBlueSorcererRobeOrange", + 5069:"YellowSorcererRobeOrange", + 5070:"LightBlueSorcererRobeOrange", + 5071:"PinkSorcererRobeOrange", + 5072:"BlackSorcererRobeOrange", + 5073:"OrangeSorcererRobeOrange", + 5074:"PurpleSorcererRobeOrange", + 5075:"DarkGreenSorcererRobeOrange", + 5076:"SorcererRobeOrange", + 5077:"RedSorcererRobePurple", + 5078:"GreenSorcererRobePurple", + 5079:"DarkBlueSorcererRobePurple", + 5080:"YellowSorcererRobePurple", + 5081:"LightBlueSorcererRobePurple", + 5082:"PinkSorcererRobePurple", + 5083:"BlackSorcererRobePurple", + 5084:"OrangeSorcererRobePurple", + 5085:"PurpleSorcererRobePurple", + 5086:"DarkGreenSorcererRobePurple", + 5087:"SorcererRobePurple", + 5088:"RedSorcererRobeDarkGreen", + 5089:"GreenSorcererRobeDarkGreen", + 5090:"DarkBlueSorcererRobeDarkGreen", + 5091:"YellowSorcererRobeDarkGreen", + 5092:"LightBlueSorcererRobeDarkGreen", + 5093:"PinkSorcererRobeDarkGreen", + 5094:"BlackSorcererRobeDarkGreen", + 5095:"OrangeSorcererRobeDarkGreen", + 5096:"PurpleSorcererRobeDarkGreen", + 5097:"DarkGreenSorcererRobeDarkGreen", + 5098:"SorcererRobeDarkGreen", + 5099:"RedSorcererRobeWhite", + 5100:"GreenSorcererRobeWhite", + 5101:"DarkBlueSorcererRobeWhite", + 5102:"YellowSorcererRobeWhite", + 5103:"LightBlueSorcererRobeWhite", + 5104:"PinkSorcererRobeWhite", + 5105:"BlackSorcererRobeWhite", + 5106:"OrangeSorcererRobeWhite", + 5107:"PurpleSorcererRobeWhite", + 5108:"DarkGreenSorcererRobeWhite", + 5109:"SorcererRobeWhite", + }; + item.nameByServerID = function(serverID) { + return serverID in items ? items[serverID] : "UNDEFINED"; + } + return item; +}(); diff --git a/public/js/comp/makeitem.sed b/public/js/comp/makeitem.sed new file mode 100755 index 0000000..45e5a91 --- /dev/null +++ b/public/js/comp/makeitem.sed @@ -0,0 +1,16 @@ +#!/bin/sed -nf +# Usage: ./makeitem.sed < item.in > item.js +1i\ +var item = function(){\ + var item = {};\ + var items = { +# /(? map.js +1i\ +var map = function(){\ + var map = {};\ + var maps = { +/^Loading Maps/,/^Maps Loaded/ { + s/^Loading Maps \[\([0-9]\+\)\/[0-9]\+\]: data\\\(.*\)\.gat/ "\1": "\2",/p; +} +$i\ + };\ + map.nameByServerID = function(serverID, date) {\ + /* TODO: Merged output format suitable for converting records running under different data */\ + return maps[serverID];\ + }\ + return map;\ +}(); diff --git a/public/js/comp/makemob.sed b/public/js/comp/makemob.sed new file mode 100755 index 0000000..c934a4e --- /dev/null +++ b/public/js/comp/makemob.sed @@ -0,0 +1,14 @@ +#!/bin/sed -nf +# Usage: ./makemob.sed < mob.in > mob.js +1i\ +var mob = function(){\ + var mob = {};\ + var mobs = { +s/^\([0-9]\+\),[\t ]\+\([^\t ]\+\),.*/ \1:"\2",/p +$i\ + };\ + mob.nameByServerID = function(serverID) {\ + return serverID in mobs ? mobs[serverID] : "UNDEFINED";\ + }\ + return mob;\ +}(); diff --git a/public/js/comp/map.js b/public/js/comp/map.js new file mode 100644 index 0000000..d845031 --- /dev/null +++ b/public/js/comp/map.js @@ -0,0 +1,114 @@ +var map = function(){ + var map = {}; + var maps = { + "0": "001-1", + "1": "001-2", + "2": "001-3", + "3": "002-1", + "4": "002-3", + "5": "002-4", + "6": "003-1", + "7": "003-2", + "8": "004-1", + "9": "004-2", + "10": "005-1", + "11": "005-3", + "12": "005-4", + "13": "006-1", + "14": "006-3", + "15": "007-1", + "16": "008-1", + "17": "009-1", + "18": "009-2", + "19": "009-3", + "20": "009-4", + "21": "009-5", + "22": "009-6", + "23": "010-1", + "24": "010-2", + "25": "011-1", + "26": "011-3", + "27": "011-4", + "28": "011-6", + "29": "012-1", + "30": "012-3", + "31": "012-4", + "32": "013-1", + "33": "013-2", + "34": "013-3", + "35": "014-1", + "36": "014-3", + "37": "015-1", + "38": "015-3", + "39": "016-1", + "40": "017-1", + "41": "017-2", + "42": "017-3", + "43": "017-4", + "44": "017-9", + "45": "018-1", + "46": "018-2", + "47": "018-3", + "48": "019-1", + "49": "019-3", + "50": "019-4", + "51": "020-1", + "52": "020-2", + "53": "020-3", + "54": "021-1", + "55": "021-2", + "56": "022-1", + "57": "024-1", + "58": "024-2", + "59": "024-3", + "60": "024-4", + "61": "025-1", + "62": "025-3", + "63": "025-4", + "64": "026-1", + "65": "027-1", + "66": "027-2", + "67": "027-3", + "68": "027-4", + "69": "028-1", + "70": "028-3", + "71": "029-1", + "72": "029-3", + "73": "030-1", + "74": "030-2", + "75": "031-1", + "76": "031-2", + "77": "031-3", + "78": "031-4", + "79": "032-1", + "80": "032-3", + "81": "033-1", + "82": "034-1", + "83": "041-1", + "84": "042-1", + "85": "042-2", + "86": "044-1", + "87": "044-3", + "88": "045-1", + "89": "046-1", + "90": "046-3", + "91": "047-1", + "92": "048-1", + "93": "048-2", + "94": "051-1", + "95": "051-3", + "96": "052-1", + "97": "052-2", + "98": "055-1", + "99": "055-3", + "100": "056-1", + "101": "056-2", + "102": "057-1", + "103": "botcheck", + }; + map.nameByServerID = function(serverID, date) { + /* TODO: Merged output format suitable for converting records running under different data */ + return maps[serverID]; + } + return map; +}(); diff --git a/public/js/comp/mob.js b/public/js/comp/mob.js new file mode 100644 index 0000000..022494e --- /dev/null +++ b/public/js/comp/mob.js @@ -0,0 +1,101 @@ +var mob = function(){ + var mob = {}; + var mobs = { + 1002:"Maggot", + 1003:"Scorpion", + 1004:"RedScorpion", + 1005:"GreenSlime", + 1006:"GiantMaggot", + 1007:"YellowSlime", + 1008:"RedSlime", + 1009:"BlackScorpion", + 1010:"Snake", + 1011:"FireGoblin", + 1012:"Spider", + 1013:"EvilMushroom", + 1014:"PinkFlower", + 1015:"SantaSlime", + 1016:"RudolphSlime", + 1017:"Bat", + 1018:"Pinkie", + 1019:"SpikyMushroom", + 1020:"Fluffy", + 1021:"CaveSnake", + 1022:"JackO", + 1023:"FireSkull", + 1024:"PoisonSkull", + 1025:"LogHead", + 1026:"MountainSnake", + 1027:"EasterFluffy", + 1028:"Mouboo", + 1029:"MauvePlant", + 1030:"CobaltPlant", + 1031:"GambogePlant", + 1032:"AlizarinPlant", + 1033:"SeaSlime", + 1034:"GrassSnake", + 1035:"Silkworm", + 1036:"Zombie", + 1037:"CloverPatch", + 1038:"Squirrel", + 1040:"Wisp", + 1041:"Snail", + 1042:"Spectre", + 1043:"Skeleton", + 1044:"LadySkeleton", + 1045:"Fallen", + 1046:"SnakeLord", + 1047:"Poltergeist", + 1049:"Bee", + 1055:"Butterfly", + 1056:"CaveMaggot", + 1057:"AngryScorpion", + 1058:"IceGoblin", + 1059:"GCMaggot", + 1060:"Archant", + 1061:"Moggun", + 1062:"Terranite", + 1063:"Pumpkin", + 1064:"Bandit", + 1065:"BanditLord", + 1066:"VampireBat", + 1067:"Reaper", + 1068:"Reaper2", + 1069:"Scythe", + 1070:"BallLightning", + 1071:"IceElement", + 1072:"Yeti", + 1073:"TheLost", + 1077:"DrunkenSkeleton", + 1078:"TipsySkeleton", + 1079:"DrunkenLadySkeleton", + 1080:"BlueSpark", + 1081:"RedSpark", + 1082:"Serqet", + 1083:"HuntsmanSpider", + 1084:"CrotcherScorpion", + 1085:"IceSkull", + 1086:"FeyElement", + 1087:"Larvern", + 1088:"Hyvern", + 1089:"HungryFluffy", + 1090:"Wolvern", + 1091:"BlueSlime", + 1092:"SlimeBlast", + 1093:"WhiteSlime", + 1094:"Reinboo", + 1095:"WhiteBell", + 1096:"SoulSnake", + 1097:"SoulEater", + 1098:"CopperSlime", + 1099:"SleepingBandit", + 1100:"AzulSlime", + 1101:"DemonicSpirit", + 1102:"Luvia", + 1103:"WitchGuard", + }; + mob.nameByServerID = function(serverID) { + return serverID in mobs ? mobs[serverID] : "UNDEFINED"; + } + return mob; +}(); diff --git a/public/js/comp/stat.js b/public/js/comp/stat.js new file mode 100644 index 0000000..cd75646 --- /dev/null +++ b/public/js/comp/stat.js @@ -0,0 +1,153 @@ +/** + * Make computations about tmwAthena's stat requirements. + * + * Amongst other things, this can be used to derive a minimum base level for a stat configuration. + */ +var stat = function(){ + var stat = {}; + var statpoint = [ + 48, + 52, + 56, + 60, + 64, + 69, + 74, + 79, + 84, + 90, + 96, + 102, + 108, + 115, + 122, + 129, + 136, + 144, + 152, + 160, + 168, + 177, + 186, + 195, + 204, + 214, + 224, + 234, + 244, + 255, + 266, + 277, + 288, + 300, + 312, + 324, + 336, + 349, + 362, + 375, + 388, + 402, + 416, + 430, + 444, + 459, + 474, + 489, + 504, + 520, + 536, + 552, + 568, + 585, + 602, + 619, + 636, + 654, + 672, + 690, + 708, + 727, + 746, + 765, + 784, + 804, + 824, + 844, + 864, + 885, + 906, + 927, + 948, + 970, + 992, + 1014, + 1036, + 1059, + 1082, + 1105, + 1128, + 1152, + 1176, + 1200, + 1224, + 1249, + 1274, + 1299, + 1324, + 1350, + 1376, + 1402, + 1428, + 1455, + 1482, + 1509, + 1536, + 1564, + 1592 + ]; + /* If a character is using a certain number of status points, what is the lowest level it could be? */ + stat.minLevelForStatusPoints = function(statusPoints) { + /* + * tmwAthena status points for a level are described by the following recurrence relation: + * p(1) = 48 + * p(l) = p(l - 1) + floor((l + 14) / 4) + * For whatever reason, the server also loads a cached copy from a file of all places - db/statpoint.txt. + * If this is out of sync with the equation, fun things can happen. + * For now, naively assume that statpoint.txt is correct and shamelessly dump it here (see above). + */ + return crossfilter.bisect.left(statpoint, statusPoints, 0, statpoint.length) + 1; + }; + var statusPointInc = [0, 0] + stat.statusPointsForStat = function(v) { + if (isNaN(v)) { + throw "Invalid input"; + } + /* First, convert the absolute stat to terms of increments. */ + /* This is as simple as removing the starting value - 1 - from it. */ + /* + * The status points needed to increase from a stat with value v can be calculated as follows: + * floor((v + 9) / 10) + 1 + */ + if (statusPointInc.length > v) { + return statusPointInc[v]; + } + var i = stat.statusPointsForStat(v - 1) + Math.floor(((v - 1) + 9) / 10) + 1; + statusPointInc.push(i); + return i; + } + /* If a character has a certain arrangement of attributes, how many status points are required for this configuration? */ + stat.statusPointsForStats = function(str, agi, vit, dex, int, luk) { + return stat.statusPointsForStat(str) + + stat.statusPointsForStat(agi) + + stat.statusPointsForStat(vit) + + stat.statusPointsForStat(dex) + + stat.statusPointsForStat(int) + + stat.statusPointsForStat(luk); + }; + /* Helper function currying minLevelForStatusPoints and stat.statusPointsForStats */ + stat.minLevelForStats = function(str, agi, vit, dex, int, luk) { + return stat.minLevelForStatusPoints(stat.statusPointsForStats(str, agi, vit, dex, int, luk)); + }; + return stat; +}(); diff --git a/public/js/crossfilter b/public/js/crossfilter new file mode 160000 index 0000000..ae994e8 --- /dev/null +++ b/public/js/crossfilter @@ -0,0 +1 @@ +Subproject commit ae994e8f8014ad4304e0575973bb23c1fb1ac0b7 diff --git a/public/js/d3 b/public/js/d3 new file mode 160000 index 0000000..91d35b4 --- /dev/null +++ b/public/js/d3 @@ -0,0 +1 @@ +Subproject commit 91d35b4205d4ef150c61c415b7379404bced7267 diff --git a/public/js/dc b/public/js/dc new file mode 160000 index 0000000..649860c --- /dev/null +++ b/public/js/dc @@ -0,0 +1 @@ +Subproject commit 649860c7f007ae41a89e1d6236748ba36292288f diff --git a/public/js/mv/chart.js b/public/js/mv/chart.js new file mode 100644 index 0000000..d06d40e --- /dev/null +++ b/public/js/mv/chart.js @@ -0,0 +1,131 @@ +var mv = function(mv) { + mv.charts = {}; + var thinWidth = 250; + var medWidth = 400; + var wideWidth = Math.max(700, document.width - thinWidth - medWidth); + mv.charter = function() { + var charter = {}; + charter.init = function() { + mv.charts.date = bar(monoGroup(wide(dc.barChart("#date-chart")), "date")) + .centerBar(true) + .elasticX(true) + .x(d3.time.scale().domain([mv.heap.date.dim.bottom(1)[0].date, mv.heap.date.dim.top(1)[0].date]).nice(d3.time.hour)) + .xUnits(d3.time.hours) + .xAxisPadding(2) + ; + mv.charts.pc = bar(monoGroup(wide(dc.barChart("#player-chart")), "pc")) + .x(d3.scale.linear().domain([mv.heap.pc.dim.bottom(1)[0].pc, mv.heap.pc.dim.top(1)[0].pc]).nice()) + ; + mv.charts.blvl = bar(monoGroup(med(dc.barChart("#blvl-chart")), "blvl")) + .x(d3.scale.linear().domain([0, mv.heap.blvl.dim.top(1)[0].pcstat.blvl])) + ; + mv.charts.type = pie(monoGroup(dc.pieChart("#type-chart"), "type")) + ; + mv.charts.target = pie(monoGroup(dc.pieChart("#target-chart"), "target")) + ; + mv.charts.wpn = pie(monoGroup(dc.pieChart("#wpn-chart"), "wpn")) + ; + mv.charts.def = pie(monoGroup(dc.pieChart("#def-chart"), "def")) + .label(function(d) { return defLevelVerbose(d.data.key); }) + .title(function(d) { return defLevelVerbose(d.data.key) + ": " + d.value; }) + .colorAccessor(function(d) { return d.data.key; }) + .colorCalculator(function(k) { switch(k) { + case 0: return "#fd350d"; + case 1: return "#fdae6b"; + case 2: return "#6baed6"; + default: throw "Definition chart: Color access key out of range!"; + }}) + .filter(2) + ; + mv.charts.map = monoGroup(margined(wide(dc.bubbleChart("#map-chart"))), "map") + .height(500) + .colorCalculator(d3.scale.category20c()) + /* X */ + .keyAccessor(function(d) { return d.value.e + 1; }) + /* Y */ + .valueAccessor(function(d) { return d.value.j + 1; }) + /* R */ + .radiusValueAccessor(function(d) { return Math.sqrt(d.value.r); }) + .maxBubbleRelativeSize(0.045) + .x(d3.scale.log().domain([1, 100000])) + .y(d3.scale.log().domain([1, 300000])) + .axisPixelPadding({left:5, top: 10, right: 15, bottom: 5}) + .elasticX(true) + .elasticY(true) + .renderHorizontalGridLines(true) + .renderVerticalGridLines(true) + .title(function(d) { return "Map " + d.key + ":" + d.value.r; }) + .renderTitle(true) + ; + mv.charts.stats = trellisChart("#stat-chart", ["str", "agi", "vit", "dex", "int", "luk"].map(function(d) { mv.heap[d].name = d; return mv.heap[d]; })); + dc.renderlet(function() { mv.charts.stats(); }); + dc.renderAll(); + } + charter.filters = function() { + var r = {}, f; + for (var k in mv.charts) { + f = mv.charts[k].filter(); + if (f != null) { + r[k] = f; + } + } + return r; + } + function defLevelVerbose(level) { + switch (level) { + case 0: return "Undefined"; + case 1: return "Mixed"; + case 2: return "Defined"; + default: console.log(d, d.data); throw "Unknown definedness case (" + d.data.key + "); this shouldn't happen"; + } + } + return charter; + }(); + function wide(chart) { + return chart + .width(wideWidth) + ; + } + function med(chart) { + return chart + .width(medWidth) + ; + } + function thin(chart) { + return chart + .width(thinWidth) + ; + } + function short(chart) { + return chart + .height(130) + ; + } + function margined(chart) { + return chart + .margins({left: 60, right: 18, top: 5, bottom: 30}) + } + function monoGroup(chart, name) { + return chart + .dimension(mv.heap[name].dim) + .group(mv.heap[name].group) + .transitionDuration(500) + ; + } + function bar(chart) { + return margined(short(chart)) + .elasticY(true) + .gap(1) + .renderHorizontalGridLines(true) + .title(function(d) { return d.key + ": " + d.value; }) + .brushOn(true) + ; + } + function pie(chart) { + return thin(chart) + .radius(90) + .colorCalculator(d3.scale.category20c()) + ; + } + return mv; +}(mv || {}); diff --git a/public/js/mv/connect.js b/public/js/mv/connect.js new file mode 100644 index 0000000..aba4c33 --- /dev/null +++ b/public/js/mv/connect.js @@ -0,0 +1,156 @@ +var mv = function(mv) { + mv.socket = { + connect: connect + }; + /* If we're updating due to something we received, then don't broadcast back. */ + var netrendering = false; + /* Our ID */ + var id = 0; + /* List of all users */ + var users = {}; + /* The user status box */ + var usersStatus = d3.select("#users-status"); + /* io.socket's socket */ + var socket; + function connect() { + socket = io.connect('http://localhost:3000'); + socket.on("connect", function() { console.log("CONNECT", arguments); }); + socket.on("disconnect", function() { console.log("DISCONNECT", arguments); }); + socket.emit('login'); + /* + * Protocol: + * selflogin -> id (I) + * login -> id (I), nick (S) + * nickset -> id (I), nick (S) + * users -> { id -> {nick (S), filters ({dim (S) -> filter (*)}) } } + * logout -> id (I) + */ + socket.on('selflogin', function(d) { + /* Acknowledged that we logged in */ + /* Take note of our ID. */ + id = d.id; + }); + socket.on('login', function(d) { + /* Someone else logging in */ + users[d.id] = { nick: d.nick, filters: {} }; + updateUsersStatus(); + }); + socket.on('users', function(d) { + /* We've got a list of all users. */ + /* The server is always right. */ + users = d.users; + updateUsersStatus(); + }); + socket.on('nickset', function(d) { + /* Someone, possibly us, changed their nick. */ + users[d.id].nick = d.nick; + updateUsersStatus(); + }); + socket.on('filterset', function(d) { + /* Someone changed their filter */ + users[d.id].filters = d.filters; + /* Use the variable netrendering to denote that we're rendering due to a change received from the network. */ + netrendering = true; + setOwnFilters(d.filters); + netrendering = false; + }); + socket.on('logout', function(d) { + /* Someone disconnected, take them off the list. */ + delete users[d.id]; + updateUsersStatus(); + }); + dc.renderlet(function() { + /* Hook a listener into dc's rendering routine. If it rerenders, broadcast the change. */ + if (netrendering) { + /* If we rendered due a change we received, don't broadcast it again. That would be A Bad Thing. */ + return; + } + socket.emit("filter", { filters: mv.charter.filters() }); + }); + } + function setOwnFilters(filters) { + /* See if there's any difference - if there isn't, don't update. */ + var change = false; + var key; + /* Check for keys in the filters to apply which are not in our charts. */ + for (key in filters) { + if (!(key in mv.charts)) + continue; + var filter = mv.charts[key].filter(); + if (typeof(filter) == "array") { + /* Crossfilter uses arrays to filter ranges. Exactly the first two elements are significant. */ + if (filter[0] == filters[key][0] && + filter[1] == filters[key][1]) { + continue; + } + } else if (filter == filters[key]) { + continue; + } + /* This filter differs. Apply it. */ + change = true; + mv.charts[key].filter(filters[key]); + } + /* Check for keys in our charts which are not in the filters to apply. */ + for (key in mv.charts) { + if (mv.charts[key].filter() != null) { + if (key in filters) { + /* This has already been handled above */ + continue; + } + /* There is no longer a filter applying here, clear it. */ + change = true; + mv.charts[key].filterAll(); + } + } + if (change) { + dc.redrawAll(); + } + } + function updateUsersStatus() { + /* Convert the user list to a form suitable for a d3 selection */ + var data = []; + for (var uid in users) { users[uid].id = uid; data.push(users[uid]); } + /* Data join */ + var userlist = usersStatus.selectAll(".user") + .data(data, function(d) { return d.id; }); + /* Enter */ + userlist + .enter().append("li").attr("class", "user") + ; + /* Update */ + userlist + .each(function(d,i) { + var elm = d3.select(this); + console.log("Userlist para appending", d,i); + var name = elm.select(".name"); + var nick = d.nick == null ? "Anonymous User " + d.id : d.nick; + if (d.id == id) { + /* This is us! We can edit our name. */ + if (name.empty()) { + console.log("Found our entry. id:", id, "datum", d); + name = elm.append("input").attr("class", "name") + .attr("type", "text") + .attr("placeholder", "Enter name here") + .on("change", function () { + /* A d3 select must be dereferenced twice to access the DOM entity */ + socket.emit("nick", { nick: name[0][0].value }); + }) + ; + } + name.attr("value", nick); + } else { + /* This is someone else. We can't edit their name. */ + if (name.empty()) { + name = elm.append("p").attr("class", "name"); + } + name.text(nick); + } + }) + ; + /* Remove */ + userlist + .exit().remove() + ; + } + return mv; +}(mv || {}); diff --git a/public/js/mv/heap.js b/public/js/mv/heap.js new file mode 100644 index 0000000..03f3c00 --- /dev/null +++ b/public/js/mv/heap.js @@ -0,0 +1,63 @@ +var mv = function(mv) { + mv.heap = function() { + var heap = {}; + var monoGroups = {}; + var statGran = 10; + heap.init = function() { + function ea(p, d) { p.e += d.e; p.j += d.j; p.r++; return p; } + function es(p, d) { p.e -= d.e; p.j -= d.j; p.r--; return p; } + function ez(p, d) { return { e: 0, j: 0, r: 0 }; } + heap.cfdata = crossfilter(mv.parser.records); + heap.all = heap.cfdata.groupAll().reduce(ea, es, ez); + monoGroup("date", function(d) { return d3.time.hour.round(d.date); }); + monoGroup("pc", function(d) { return d.pc; }); + monoGroup("map", function(d) { return d.map; }).reduce(ea, es, ez); + monoGroup("blvl", function(d) { return d.pcstat ? d.pcstat.blvl : 0; }); + monoGroup("type", function(d) { return d.type; }); + monoGroup("target", function(d) { return d.target; }); + monoGroup("wpn", function(d) { return d.wpn; }); + function sa(p, d) { + if (!d.pcstat) return p; + p.str[d.pcstat.str]++ || (p.str[d.pcstat.str] = 1); + p.agi[d.pcstat.agi]++ || (p.agi[d.pcstat.agi] = 1); + p.vit[d.pcstat.vit]++ || (p.vit[d.pcstat.vit] = 1); + p.dex[d.pcstat.dex]++ || (p.dex[d.pcstat.dex] = 1); + p.int[d.pcstat.int]++ || (p.int[d.pcstat.int] = 1); + p.luk[d.pcstat.luk]++ || (p.luk[d.pcstat.luk] = 1); + return p; + } + function ss(p, d) { + if (!d.pcstat) return p; + --p.str[d.pcstat.str] || (p.str[d.pcstat.str] = undefined); + --p.agi[d.pcstat.agi] || (p.agi[d.pcstat.agi] = undefined); + --p.vit[d.pcstat.vit] || (p.vit[d.pcstat.vit] = undefined); + --p.dex[d.pcstat.dex] || (p.dex[d.pcstat.dex] = undefined); + --p.int[d.pcstat.int] || (p.int[d.pcstat.int] = undefined); + --p.luk[d.pcstat.luk] || (p.luk[d.pcstat.luk] = undefined); + return p; + } + function sz(p, d) { return { str: [], agi: [], vit: [], dex: [], int: [], luk: [] }; } + monoGroup("str", function(d) { return d.pcstat ? d.pcstat.str : 0; }).reduce(sa, ss, sz); + monoGroup("agi", function(d) { return d.pcstat ? d.pcstat.agi : 0; }).reduce(sa, ss, sz); + monoGroup("vit", function(d) { return d.pcstat ? d.pcstat.vit : 0; }).reduce(sa, ss, sz); + monoGroup("dex", function(d) { return d.pcstat ? d.pcstat.dex : 0; }).reduce(sa, ss, sz); + monoGroup("int", function(d) { return d.pcstat ? d.pcstat.int : 0; }).reduce(sa, ss, sz); + monoGroup("luk", function(d) { return d.pcstat ? d.pcstat.luk : 0; }).reduce(sa, ss, sz); + /* Debugging group */ + /* + * How well defined a record is. + * 0 -> Record contains undefined data + * 1 -> Record is defined, but undefined records follow and may impede validity of findings + * 2 -> Record and all succeeding records are well defined + */ + monoGroup("def", function(d) { if (d.pcstat == undefined) { return 0; } if (d.date <= mv.parser.fullyDefinedCutoff()) { return 1; } return 2; }); + heap.def.dim.filterExact(2); + } + function monoGroup(name, mapping) { + heap[name] = {}; + return heap[name].group = (heap[name].dim = heap.cfdata.dimension(mapping)).group(); + } + return heap; + }(); + return mv; +}(mv || {}); diff --git a/public/js/mv/load.js b/public/js/mv/load.js new file mode 100644 index 0000000..39dd391 --- /dev/null +++ b/public/js/mv/load.js @@ -0,0 +1,60 @@ +var mv = function(mv) { + mv.loader = function() { + /* Set up handlers for file selector */ + var numfiles = 0; + var filenames = []; + var curfile = 0; + var loader = {}; + loader.onbulkstart = function(fevt) {}; + loader.onloadstart = function(evt) {}; + loader.onprogress = function(evt) {}; + loader.onabort = function(evt) { + alert('File load aborted!'); + }; + loader.onerror = function(evt) { + switch(evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + alert('File Not Found!'); + break; + case evt.target.error.NOT_READABLE_ERR: + alert('File is not readable'); + break; + case evt.target.error.ABORT_ERR: + break; // noop + default: + alert('An error occurred reading this file.'); + }; + }; + loader.numfiles = function() { return numfiles; }; + loader.filenames = function() { return filenames; }; + loader.curfile = function() { return curfile; }; + loader.init = function(each, after) { + document.getElementById('input').addEventListener('change', function(fevt) { + numfiles = fevt.target.files.length; + filenames = Array.prototype.map.call(fevt.target.files, function(d) { return d.name; }); + curfile = 0; + var reader = new FileReader(); + loader.onbulkstart(fevt); + reader.onerror = function() { loader.onerror.apply(null, arguments) }; + reader.onprogress = function() { loader.onprogress.apply(null, arguments) }; + reader.onabort = function() { loader.onabort.apply(null, arguments) }; + reader.onloadstart = function() { loader.onloadstart.apply(null, arguments) }; + reader.onload = function(evt) { + each(reader.result); + ++curfile; + if (curfile == numfiles) { + after(); + } else { + nextFile(); + } + }; + function nextFile() { + reader.readAsBinaryString(fevt.target.files[curfile]); + } + nextFile(); + }, false); + }; + return loader; + }(); + return mv; +}(mv || {}); diff --git a/public/js/mv/main.js b/public/js/mv/main.js new file mode 100644 index 0000000..b7fcb1e --- /dev/null +++ b/public/js/mv/main.js @@ -0,0 +1,59 @@ +var mv = function(mv) { + mv.init = function() { + console.log("Initialising"); + var loadbar = progress('loadbar'); + var filesbar = progress('filesbar'); + var lbase = loadbar.label; + loadbar.label = function() { + return lbase() == '100%' ? "Loaded '" + mv.loader.filenames()[mv.loader.curfile()]+ "' - Done!" : "Loading '" + mv.loader.filenames()[mv.loader.curfile()] + "' - " + lbase(); + }; + var fbase = filesbar.label; + filesbar.label = function () { + return fbase() == '100%' ? "Loaded " + mv.loader.numfiles() + " file(s) - Done!" : "Loading file " + mv.loader.curfile() + " of " + mv.loader.numfiles() + " - " + fbase(); + } + mv.loader.onbulkstart = function(fevt) { + loadbar.show(); + filesbar.show(); + }; + mv.loader.onloadstart = function(evt) { + filesbar.update(mv.loader.curfile(), mv.loader.numfiles()); + loadbar.reset(); + }; + mv.loader.onprogress = function(evt) { + if (evt.lengthComputable) { + loadbar.update(evt.loaded, evt.total); + } + }; + mv.loader.init(handleFile, postLoading); + function handleFile(data, curFileNum, numFiles) { + loadbar.complete(); + mv.parser.parseRecords(data); + } + function postLoading() { + filesbar.complete(); + /* TODO: This is still a total mess that doesn't really transition properly */ + setTimeout(function() { + loadbar.hide(); + }, 2000); + mv.heap.init(); + setTimeout(function() { + filesbar.hide(); + d3.select("#mask") + .transition() + .style("opacity", 0) + .remove(); + d3.selectAll(".vis-hide") + .style("display", "inline") + .transition() + .style("opacity", 1) + ; + mv.charter.init(); + console.log(document.getElementById("connect-option").checked); + if (document.getElementById("connect-option").checked) { + mv.socket.connect(); + } + }, 2000); + } + }; + return mv; +}(mv || {}); diff --git a/public/js/mv/parse.js b/public/js/mv/parse.js new file mode 100644 index 0000000..8094bc5 --- /dev/null +++ b/public/js/mv/parse.js @@ -0,0 +1,109 @@ +var mv = function(mv) { + mv.parser = function() { + var parser = {}; + var pcstat = {}; + var fullyDefinedCutoff = 0; + parser.records = []; + parser.fullyDefinedCutoff = function() { return fullyDefinedCutoff; }; + parser.parseRecords = function(data) { + var spl = data.split(/\r?\n/); + spl.forEach(function(e, i) { + var d; + d = e.match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); + if (d) { + var mapSID = parseInt(d[3]); + var ts = new Date(0); + ts.setUTCSeconds(d[1]); + var rec = { + date: ts, + pc: parseInt(d[2]), + map: map.nameByServerID(parseInt(d[3]), ts), + x: parseInt(d[4]), + y: parseInt(d[5]), + e: parseInt(d[6]), + j: parseInt(d[7]), + type: d[8], + pcstat: pcstat[d[2]], + target: "UNKNOWN", + dmg: -1010, + wpn: "UNKNOWN", + atktype: "UNKNOWN" + }; + if (pcstat[d[2]] == undefined && (!fullyDefinedCutoff || ts > fullyDefinedCutoff)) { + fullyDefinedCutoff = ts; + } + /* XXX: Fragile horrible and unstructured, this whole thing needs a rewrite really */ + if (i >= 2 && rec.type == "KILLXP") { + d = spl[i - 1].match(/^(\d+\.\d+) MOB(\d+) DEAD/); + if (d) { + var mID = parseInt(d[2]); + /* There's a massive wealth of data that can be collected from this. Number of assailants, weapons used, the relationships with the assailants... this can't be done with a simple lookbehind. For now, just extract what mob it was, and what the killing weapon used was. */ + d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) WPNDMG MOB(\d+) (\d+) FOR (\d+) WPN (\d+)/); + if (d) { + softAssert(mID == parseInt(d[6]), "Integrity error: MOB ID mismatch!"); + // softAssert(rec.pc == parseInt(d[2]), "Integrity error: PC ID mismatch!"); + rec.target = mob.nameByServerID(d[7]); + softAssert(rec.target, "Unknown target!") + rec.dmg = parseInt(d[8]); + rec.wpn = item.nameByServerID(d[9]); + rec.atktype = "Physical"; + } else { + /* Not weapon damage, perhaps it was spell damage? */ + d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) SPELLDMG MOB(\d+) (\d+) FOR (\d+) BY ([^ ]+)/); + if (d) { + rec.target = mob.nameByServerID(d[7]); + rec.dmg = parseInt(d[8]); + rec.wpn = d[9]; + rec.atktype = "Magical"; + } +// console.error("No match (deathblow):", spl[i - 2]); + } + } else { + d = spl[i - 1].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); + if (d) { + var clone = parser.records[parser.records.length - 1]; + softAssert(rec.map == clone.map, "Integrity error: MAP ID mismatch!"); + rec.target = clone.target; + softAssert(rec.target, "Unknown (cloned) target!"); + rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */ + rec.wpn = clone.wpn; + rec.atktype = clone.atktype; /* FIXME: Take into account what the assists used */ + } else { +// console.error("No match (clone):", spl[i - 1]); + } + } + } + parser.records.push(rec); + return; + } + d = e.match(/^(?:\d+\.\d+) PC(\d+) (?:\d+):(?:\d+),(?:\d+) STAT (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) /); + if (d) { + var s = { + str: parseInt(d[2]), + agi: parseInt(d[3]), + vit: parseInt(d[4]), + int: parseInt(d[5]), + dex: parseInt(d[6]), + luk: parseInt(d[7]) + }; + s.blvl = stat.minLevelForStats(s.str, s.agi, s.vit, s.int, s.dex, s.luk); + s.str = Math.floor(s.str / 10); + s.agi = Math.floor(s.agi / 10); + s.vit = Math.floor(s.vit / 10); + s.int = Math.floor(s.int / 10); + s.dex = Math.floor(s.dex / 10); + s.luk = Math.floor(s.luk / 10); + pcstat[d[1]] = s; + return; + } + }); + }; + function softAssert(expr, msg) { + if (!expr) { + console.error("SOFTASSERT FAILURE: " + msg); + } + } + return parser; + }(); + return mv; +}(mv || {}); diff --git a/public/js/util/memoize.js b/public/js/util/memoize.js new file mode 100644 index 0000000..796c060 --- /dev/null +++ b/public/js/util/memoize.js @@ -0,0 +1,45 @@ +/* + * Helper function for memoization + * + * The passed labeller function must generate unique and exact names for entries that are naturally equal. + * IDs are persistent. + * + * names, idByName, and entries are accessible fields from the memoizer. + * Behaviour on external modification of these fields is undefined. + * + * An entry object passed to the memoizer has its field "id" set to the memoized id by default. + * The name of the ID field can be modified by calling the getter/setter idField() from the memoizer. + * Behaviour on changing the ID field used while the memoizer has already performed memoization is undefined. + */ +function memoize(labeller) { + var idField = "id"; + var names = []; + var idByName = {}; + var entries = []; + var _memoizer = function(entry) { + var name = labeller(entry); + if (name in idByName) { + /* We already have a suitable entry. */ + var id = idByName[name]; + return entries[id]; + } + /* New entry. */ + /* Set the entry's ID. */ + entry[idField] = entries.length; + /* Index the new entry. */ + idByName[name] = entry[idField]; + names[entry[idField]] = name; + entries.push(entry); + + return entry; + } + _memoizer.names = function() { return names; }; + _memoizer.idByName = function() { return idByName; }; + _memoizer.entries = function() { return entries; }; + _memoizer.idField = function(x) { + if (!arguments.length) return idField; + idField = x; + return _memoizer; + }; + return _memoizer; +} diff --git a/public/js/util/progress.js b/public/js/util/progress.js new file mode 100644 index 0000000..060e6dc --- /dev/null +++ b/public/js/util/progress.js @@ -0,0 +1,37 @@ +function progress(root) { + var _progress = {}; + var container = document.getElementById(root); + var _percent = '0%'; + var bar = document.querySelector('#' + root + ' .percent'); + _progress.label = function() { + return _percent; + } + /* Updates the progress bar to display a specific percentage. No range checking performed. */ + _progress.setPercent = function(percent) { + _percent = percent; + bar.style.width = _percent; + bar.textContent = _progress.label(); + } + /* Updates the progress bar to display a percentage based on the current proportion of items done. */ + _progress.update = function(current, total) { + var percentLoaded = Math.min(100, Math.round((current / total) * 100)); + _progress.setPercent(percentLoaded + '%'); + }; + /* Resets the progress bar to display nothing done. */ + _progress.reset = function() { + _progress.setPercent('0%'); + } + /* Resets the progress bar to display everything done. */ + _progress.complete = function() { + _progress.setPercent('100%'); + } + /* Shows the progress bar. */ + _progress.show = function() { + container.className += ' loading'; + } + /* Hides the progress bar */ + _progress.hide = function() { + container.className = container.className.replace(/\bloading\b/, ''); + } + return _progress; +} diff --git a/public/js/util/trellis-chart.js b/public/js/util/trellis-chart.js new file mode 100644 index 0000000..65e07a2 --- /dev/null +++ b/public/js/util/trellis-chart.js @@ -0,0 +1,140 @@ +function trellisChart(anchor, monoGroups) { + /* attr -> {dim, group} key -> str amount, value -> { str, agi, vit, dex, int, luk } */ + + var attrs = monoGroups.map(function(d) { return d.name; }); + var attrsIdByName = {}; + monoGroups.forEach(function(d, i) { attrsIdByName[d.name] = i; }); + + var cellWidth = 5; + var radius = cellWidth / 2; + var subChartLength = 57; + var subChartUnpaddedLength = 50; + var subChartPadding = 7; + var filler = d3.scale.log().domain([1, 2]).range([0, 255]); + + var margin = {top: 10, right: 10, bottom: 20, left: 10}; + var anchor = d3.select(anchor); + var g = anchor.select("g"); + + var _chart = function() { + if (g.empty()) { + /* Make stuff! */ + var svg = anchor.append("svg"); + g = svg + .append("g"); + attrs.forEach(function(d, i) { + g + .append("text") + .attr("transform", function(d) { return "translate(0," + ((attrs.length - i) * subChartLength + 10 - subChartLength / 2) + ")"; }) + .text(d) + ; + g + .append("text") + .attr("transform", function(d) { return "translate(" + (i * subChartLength + 25 + 22) + "," + (attrs.length * subChartLength + 18) + ")"; }) + .text(d) + ; + }) + g = svg + .append("g") + .attr("transform", "translate(" + (margin.left + 25) + "," + (margin.top) + ")"); + } + /* Group first into columns for each stat. We have one column for each of the stat monoGroups. */ + /* + * monoGroups is an array of each stat dimension. We can consider each column to have data in the following format: + * { group: function, dim: function, name: stat } + */ + var columns = g.selectAll(".column") + .data(monoGroups); + var colE = columns + .enter().append("g") + .attr("class", "column") + .attr("transform", function(d) { return "translate(" + (attrsIdByName[d.name] * subChartLength) + ",0)"; }) + ; + colE + .append("line") + .attr("x1", -cellWidth) + .attr("x2", -cellWidth) + .attr("y1", -cellWidth) + .attr("y2", subChartLength * attrs.length - subChartPadding) + .attr("class", "border-line") + ; + colE + .append("line") + .attr("x1", subChartUnpaddedLength) + .attr("x2", subChartUnpaddedLength) + .attr("y1", -cellWidth) + .attr("y2", subChartLength * attrs.length - subChartPadding) + .attr("class", "border-line") + ; + /* Each stat has an array for its value. Group these to find the x position. */ + /* + * The function transforms the data to take the grouping. We can consider each x position grouping to have data in the following format: + * { key: position, value: [{[stat] -> [y pos] -> count}] } + */ + var colposg = columns.selectAll(".colpos") + .data(function(d, i) { +// console.log("Incoming colposg format:", d, i, "Transformed to:", d.group.all().map(function(d2) { d2.name = d.name; return d2; })); + return d.group.all().map(function(d2) { d2.name = d.name; return d2; }); + }, function(d) { return d.key; }); + colposg + .enter().append("g") + .attr("class", "colpos") + .attr("transform", function(d) { return "translate(" + (d.key * cellWidth) + ",0)"; }) + ; + /* Next, split up each x position grouping into its y stat grouping. */ + /* + * We can consider each y stat grouping to have data in the following format: + * v[y pos] -> count; v.name -> name + */ + var rows = colposg.selectAll(".row") + .data(function(d, i) { +// console.log("Incoming row format:", d, i, "Transformed to:", attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; })); + return attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; }); + }); + rows + .enter().append("g") + .attr("class", "row") + .attr("transform", function(d) { return "translate(0," + ((attrs.length - attrsIdByName[d.name] - 1) * subChartLength) + ")"; }) + ; + /* Finally, split up each y stat grouping into x. */ + var vmax = 0; + var cells = rows.selectAll(".cell") + .data(function(d, i) { +// console.log("Incoming cells format:", d, i, "Transformed to:", d.data); + return d.data; + }); + cells + .enter().append("circle") + .attr("class", "cell") + .attr("r", radius) + ; + cells + .each(function(d) { + if (d > vmax) vmax = d; + }) + ; + filler.domain([1, vmax + 1]); + cells + .attr("fill", function(d) { + return d ? d3.rgb(255 - filler(d + 1), 255 - filler(d + 1) / 2, 255 - filler(d + 1) / 3) : "white"; + }) + .attr("transform", function(d, i) { return "translate(0," + ((10-i) * cellWidth) + ")"; }) + ; + cells + .exit() + .remove() + ; + } + + _chart.filter = function() { + /* + * TODO: + * This is going to be interesting. As the chart is not charting a single + * monogroup, and most code is built around this assumption, this might + * well end up being a messy special case. + */ + return null; + } + + return _chart; +} -- cgit v1.2.3-60-g2f50