Created
August 31, 2011 09:51
-
-
Save johan/1183201 to your computer and use it in GitHub Desktop.
Recettear item data by category
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html;charset=utf-8"> | |
<title>Recettear Item Data</title> | |
<script src="http://mbostock.github.com/d3/d3.js?2.0.3"></script> | |
<script src="http://mbostock.github.com/d3/d3.geom.js?2.0.3"></script> | |
<script src="http://mbostock.github.com/d3/d3.layout.js?2.0.3"></script> | |
<link href="styles.css" rel="stylesheet" type="text/css"/> | |
</head> | |
<body> | |
<ul class="characters clearfix"> | |
<li class="selected"> | |
<a href="#Anyone" class="Anyone" title="Anyone" id="Anyone" | |
onclick="by_character(event)">Anyone</a> | |
</li> | |
</ul> | |
<div id="chart"></div> | |
<ul class="dungeons clearfix"></ul> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var r = 960 | |
, w = 960 | |
, h = 960 // 500 | |
, vis = d3.select('#chart').append('svg:svg') | |
.attr('width', r) | |
.attr('height', r) | |
.attr('class', 'bubble') | |
, defs = vis.append('svg:defs') | |
, style = document.createElement('style') | |
, head = document.head || document.getElementsByTagName('head')[0] | |
, css = '' | |
, pref = 'file:' === location.protocol ? '' : 'http://bl.ocks.org/d/1306472/' | |
, prel = new Image // preload the images | |
, surl = prel.src = pref + 'sprites.png' | |
, char_radius = 33 | |
, char_img_sz = 80 | |
, sprite_w = 1025 | |
, sprite_h = 208 | |
, _slice = Array.prototype.slice | |
, C, I, D // character, item and dungeon visualizations respectively | |
, colours = | |
{ "LouieCharmeCaillouTielleElanNagiGriffArma": "#BBB" | |
, "LouieCaillouElanGriff": "lightblue" | |
, "CharmeTielleNagiArma": "pink" | |
} | |
, data, item_urls | |
, items, characters, dungeons, locations, categories, recipes, sprites | |
; | |
d3.select(self.frameElement).style('height', (80 + w + 128) +'px'); | |
load([ pref + 'recettear-data.json' | |
, pref + 'recettear-images.json'], d3.json, loaded); | |
function loaded(files) { | |
data = files[0]; | |
item_urls = files[1]; // url : array of item indices with that image | |
items = data.items; | |
characters = data.characters; | |
dungeons = data.dungeons; | |
locations = data.locations; | |
categories = data.categories; | |
recipes = data.recipes; | |
sprites = data.sprites; | |
characters.forEach(function(c) { colours[c.name] = c.color; }); | |
init(); | |
} | |
function init() { | |
var items_pane = d3.select('.items') | |
, items_node = items_pane.node() | |
, max_height | |
, defs = vis.append('svg:defs') | |
, i = 0 | |
; | |
for (var url in item_urls) item_urls[url].forEach(function(idx) { | |
var item = items[idx], id; | |
item.value = sum_stats(item); | |
item.img_id = id = 'i' + i++; | |
item.is_item = 1; | |
defs.append('svg:image') | |
.attr('id', id) | |
.attr('width', 1) | |
.attr('height', 1) | |
.attr('xlink:href', url); | |
}); | |
// sort characters by sex, to make some item usage distribution more intuitive | |
characters.sort(function by_sex(a, b) { | |
var A = Number(a.id.slice(2)), B = Number(b.id.slice(2)); | |
return (((A & 1) << 3) + A) - (((B & 1) << 3) + B); | |
}); | |
dungeons.forEach(function (d) { | |
var dx = (d.id - 1) * -128 + 4; | |
css += '.du'+ d.id +' { background-position: '+ dx +'px 0px; }\n'; | |
d.is_dungeon = true; | |
}); | |
style.innerHTML = css; | |
head.appendChild(style); | |
// for character selection (and showing users of items) | |
C = d3.select('ul.characters') | |
.selectAll('li.character').data(characters) | |
.enter().append('li') // "anyone" is in the document already | |
.attr('class', 'character') | |
.append('a') | |
.text(_name) | |
.attr('id', _name) // permalinks | |
.attr('class', _id) // (styling) | |
.attr('title', _name) | |
.attr('onclick', 'by_character(event)') | |
.attr('href', function(c) { return '#'+ c.name; }); | |
bubbles(); | |
I = d3.select('#chart').selectAll('.item'); | |
/* | |
// the main items pane | |
I = items_pane.selectAll('.item').data(items).enter().append('a') | |
.attr('class', 'item') | |
.attr('href', wiki_url) | |
.attr('id', _item_id) | |
.attr('title', _name); | |
// add item image icon | |
I.append('img').attr('width', 32).attr('height', 32).attr('src', _image); | |
// make items findable via Ctrl/Cmd-F (centering titles below) | |
I.append('label') | |
.text(_name) | |
.attr('for', _item_id) | |
.style('margin-left', function() { | |
return - (this.offsetWidth >> 1) +'px'; | |
}); | |
*/ | |
// the dungeon selector | |
D = d3.select('ul.dungeons') | |
.selectAll('li.dungeon').data(dungeons) | |
.enter().append('li') | |
.attr('class', function(d) { return 'dungeon du'+ d.id; }); | |
// make chested items findable by dungeon (and set dungeon expectations) | |
D.append('a') | |
.text(_name) | |
.attr('id', _name) | |
.attr('title', _name) | |
.attr('onclick', 'by_dungeon(event)') | |
.attr('href', function(d) { return '#'+ d.name; }); | |
max_height = window.innerHeight - y_pos(items_node) - 8; | |
// 64 = item badge height, 120 = dungeon icon height | |
items_node.style.height = (max_height - max_height % 64 - 120) + 'px'; | |
document.body.addEventListener('mousemove', show_item, false); | |
document.body.addEventListener('DOMFocusIn', show_item, false); | |
by_character((location.hash || '').slice(1)); | |
} | |
function bubbles() { | |
var format = d3.format(",d") | |
, fill = d3.scale.category20c() | |
; | |
var bubble = d3.layout.pack().sort(null).size([r, r]); | |
var node = vis.selectAll('g.node') | |
.data(bubble.nodes({ children: items.filter(pluck('value')) })) | |
.enter().append('svg:g') | |
.attr('class', 'node item') | |
.attr('transform', function(d) { | |
return 'translate('+ d.x +','+ d.y +')'; | |
}); | |
//node.append('svg:title') | |
// .text(function(d) { return d.name; }); | |
node.append('svg:circle') | |
.attr('class', 'item') | |
.attr('r', function(d) { return d.r; }) | |
.style('fill', function(d) { | |
var chars = d.chars && d.chars.join(''); | |
return chars ? colours[chars] || '#EEE' : 'white'; | |
}); | |
node.append('svg:use') | |
.attr('class', 'item') | |
.attr('xlink:href', function(d) { return '#'+ d.img_id; }) | |
.attr('transform', function(d) { | |
var dx = Math.sqrt(d.r * d.r / 2) | |
, sz = 'scale('+ (2 * dx) +')'; | |
return 'translate(-'+ dx +',-'+ dx +') '+ sz; | |
}) | |
.append('svg:title') | |
.text(function(d) { return d.name +': '+ d.value +' total stats'; }); | |
//node.append('svg:text') | |
// .attr('text-anchor', 'middle') | |
// .attr('dy', '.3em') | |
// .text(function(d) { return d.className.substring(0, d.r / 3); }); | |
} | |
function show_item(e) { | |
function is_user(ch) { | |
if (!item || !item.chars) return false; | |
return -1 !== item.chars.indexOf(ch.name); | |
} | |
var at = e.target, item = at.__data__; | |
if (item && !item.is_item) item = false; | |
C.classed('user', is_user); | |
// if we're hovering the dungeon panel, don't touch it | |
if (!item || item.is_dungeon) return; | |
D.classed('chest', function(d) { | |
return item && is_in_dungeon(d.id, item); | |
}); | |
} | |
function y_pos(node) { | |
var pn = node.offsetParent || 0; | |
return node.offsetTop + (pn && y_pos(pn)); | |
} | |
function _id(c) { return c.id; } | |
function _name(c) { return c.name; } | |
function _image(i) { return i.image; } | |
function pluck(n) { return function(x) { return x && x[n]; }; } | |
function array(a, n) { return _slice.call(a, n||0); } | |
function partial(fn) { | |
var args = array(arguments, 1); | |
return function() { return fn.apply(this, args.concat(array(arguments))); }; | |
} | |
function _item_id(i) { return 'i'+ i.id; } | |
function wiki_url(item) { | |
var name = ( | |
{ "Assassin Blade": "Assassin's Blade" | |
})[item.name] || item.name; | |
return 'http://recettear.wikia.com/wiki/' + name.replace(/ /g, '_'); | |
} | |
function sum_stats(i) { | |
return i.atk + i.def + i.mag + i.mdef; | |
} | |
function by_dungeon(e) { | |
var name = 'object' === typeof e ? e.target.id : e | |
, x = window.pageXOffset | |
, y = window.pageYOffset | |
, id, d, i, min, max; | |
for (i = 0; d = dungeons[i]; i++) | |
if (d.name === name) { | |
id = d.id; | |
break; | |
} | |
if (!id) return; | |
min = 10 * id; | |
max = 10 + min; | |
by_character('Anyone'); // reset character view | |
D.classed('selected', 0).classed('chest', 0); | |
d3.select(document.getElementById(name).parentNode).classed('selected', 1); | |
I.transition().duration(250) | |
.style('opacity', function(item) { | |
return item.is_item ? is_in_dungeon(id, item) ? 1 : 0.25 : 1; | |
}); | |
} | |
function is_in_dungeon(no, item) { | |
function exists(min_level) { | |
return min <= min_level && min_level < max; | |
} | |
var min = no * 10, max = 10 + min; | |
return item && (item.where.chest || []).filter(exists).length; | |
} | |
function by_character(e) { | |
var name = 'object' === typeof e ? e.target.id : e | |
, any = 'Anyone' === name | |
, x = window.pageXOffset | |
, y = window.pageYOffset | |
, id, c, i; | |
for (i = 0; c = characters[i]; i++) | |
if (c.name === name) { | |
id = c.id; | |
break; | |
} | |
if (!id && !any) return; | |
//console.info(name); | |
D.classed('chest', 0).classed('selected', 0); | |
d3.select('.characters li.selected').classed('selected', 0); | |
d3.select(document.getElementById(name).parentNode).classed('selected', 1); | |
I.transition() | |
.duration(250) | |
.style('opacity', function(d, i) { | |
if (d && d.is_item && !any && -1 === (d.chars || []).indexOf(name)) { | |
return 0.25; | |
} | |
return 1; | |
}) | |
; | |
// don't change on-screen scroll position when clicked | |
setTimeout(function() { top.scrollTo(x, y); }, 0); | |
} | |
function load(urls, loader, cb) { | |
function fetch(url, n) { | |
function loaded(data) { | |
all[n] = data; | |
if (!--left) cb(all); | |
} | |
loader(url, loaded); | |
} | |
var all = [], left = urls.length; | |
all.urls = urls; | |
urls.forEach(fetch); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body,html { margin: 0; } | |
circle { | |
stroke: #fff; | |
stroke-width: 1.5px; | |
} | |
circle.node { | |
stroke: #fff; | |
stroke-width: 1.5px; | |
} | |
line.link { | |
stroke: #999; | |
stroke-opacity: .6; | |
} | |
.foot { display: none; } | |
.items { | |
margin: 8px 0; | |
width: 736px; | |
overflow-y: scroll; | |
overflow-x: hidden; | |
} | |
/* | |
.item { | |
float: left; | |
display: block; | |
margin: 0 2px 7px; | |
width: 32px; | |
height: 32px; | |
padding: 12px 15px 12px 14px; | |
background: url("http://johan.github.com/d3/ex/recettear/gfx/misc/badge.png") no-repeat; | |
position: relative; | |
color: transparent; | |
} | |
.item .icon { | |
width: 32px; | |
height: 32px; | |
} | |
.item label { | |
position: absolute; | |
left: 30px; | |
bottom: -10px; | |
white-space: nowrap; | |
} | |
.item:focus label { | |
color: #000; | |
} | |
*/ | |
.clearfix:after { | |
visibility: hidden; | |
display: block; | |
clear: both; | |
content: "."; | |
height: 0; | |
} | |
/* horizontal character selection bar */ | |
.characters, .dungeons { | |
margin: 0 auto; | |
padding: 0; | |
width: 720px; | |
height: 80px; | |
} | |
.dungeons { | |
width: 768px; | |
height: 120px; | |
} | |
.characters li, .dungeons li { | |
float: left; | |
display: block; | |
list-style-image: none; | |
width: 80px; | |
height: 80px; | |
} | |
.dungeons li, .dungeons li a { | |
display: block; | |
width: 120px; | |
height: 120px; | |
} | |
.dungeons li { | |
background-image: url("http://johan.github.com/d3/ex/recettear/gfx/misc/dungeons.png"); | |
} | |
.characters li.selected { | |
background: url("http://johan.github.com/d3/ex/recettear/gfx/misc/avatar-bg.png"); | |
} | |
.characters li.selected :hover { | |
background-color: transparent; | |
} | |
.characters a { | |
display: block; | |
float: left; | |
width: 80px; | |
height: 80px; | |
background-color: transparent; | |
color: transparent; | |
text-align: center; /* center name */ | |
line-height: 160px; /* below image */ | |
} | |
.characters a:focus { | |
color: #000; | |
} | |
.character a { | |
background-image: url("http://johan.github.com/d3/ex/recettear/gfx/misc/avatars.png"); | |
} | |
.dungeons a { | |
display: block; | |
float: left; | |
width: 120px; | |
height: 120px; | |
background-color: transparent; | |
color: transparent; | |
text-align: center; /* center name */ | |
line-height: 240px; /* below image */ | |
overflow-y: hidden; | |
} | |
.dungeons a:focus { | |
color: #000; | |
} | |
/* "item X appears in a chest of dungeon Y" */ | |
.dungeon.chest a { | |
background: url("http://johan.github.com/d3/ex/recettear/gfx/misc/chest.png") no-repeat bottom right; | |
} | |
/* sub-ideal; there's cognitive dissonance when you select a dungeon, | |
* hover an item that isn't in that dungeon's chests, and the icon is | |
* still lit. Any graphics artists out there that want to improve this? */ | |
.dungeon.selected a { | |
background: url("http://johan.github.com/d3/ex/recettear/gfx/misc/chest.png") no-repeat bottom right; | |
} | |
.Anyone, .Anyone:hover { | |
background-image: none; | |
background-color: #BBB; | |
} | |
/* Louie */ | |
.user.ch00, .ch00:hover { | |
background-color: #B3A66F; | |
} | |
/* Charme */ | |
.user.ch01, .ch01:hover { | |
background-color: #87849A; | |
} | |
.characters .ch01 { | |
background-position: -80px 0; | |
} | |
/* Caillou */ | |
.user.ch02, .ch02:hover { | |
background-color: #809486; | |
} | |
.characters .ch02 { | |
background-position: -160px 0; | |
} | |
/* Tielle */ | |
.user.ch03, .ch03:hover { | |
background-color: #DC8659; | |
background-position: -240px 0; | |
} | |
.characters .ch03 { | |
background-position: -240px 0; | |
} | |
/* Elan */ | |
.user.ch04, .ch04:hover { | |
background-color: #E3D194; | |
} | |
.characters .ch04 { | |
background-position: -320px 0; | |
} | |
/* Nagi */ | |
.user.ch05, .ch05:hover { | |
background-color: #7E6051; | |
} | |
.characters .ch05 { | |
background-position: -400px 0; | |
} | |
/* Griff */ | |
.user.ch06, .ch06:hover { | |
background-color: #42373D; | |
} | |
.characters .ch06 { | |
background-position: -480px 0; | |
} | |
/* Arma */ | |
.user.ch07, .ch07:hover { | |
background-color: #5F6934; | |
} | |
.characters .ch07 { | |
background-position: -560px 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment