Skip to content

Instantly share code, notes, and snippets.

@johan
Created August 31, 2011 09:51
Show Gist options
  • Save johan/1183201 to your computer and use it in GitHub Desktop.
Save johan/1183201 to your computer and use it in GitHub Desktop.
Recettear item data by category
<!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>
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);
}
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