Skip to content

Instantly share code, notes, and snippets.

@ralphbean
Last active December 28, 2015 21:59
Show Gist options
  • Save ralphbean/7567926 to your computer and use it in GitHub Desktop.
Save ralphbean/7567926 to your computer and use it in GitHub Desktop.
A dynamic logo for fedmsg notifications

This is an idea for a dynamic logo for the embryonic fedmsg-notifications system.

A development instance is available at http://209.132.184.212/

path.link {
fill: none;
stroke: #294172;
stroke-width: 1.5px;
}
marker#output {
fill: #3c6eb4;
}
path.link.output {
stroke: #3c6eb4;
stroke-dasharray: 5,5;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
.node image {
// Can we make the images circular? Not with css apparently.
}
// It would be cool to somehow differentiate these things
.group-1 {
}
.group-2 {
}
.group-3 {
}
/* Generate this with a python script */
// Compute the distinct nodes from the links.
//links.forEach(function(link) {
// link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
// link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
//});
var w = 600,
h = 600;
d3.json("graph.json", function(json) {
var force = d3.layout.force()
.nodes(json.nodes)
.links(json.links)
.size([w, h])
.linkDistance(60)
.charge(-600)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
// Per-type markers, as they don't inherit styles.
svg.append("svg:defs").selectAll("marker")
.data(["input", "output"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var node = svg.append("svg:g").selectAll(".node")
.data(force.nodes())
.enter().append("g").attr("class", "node")
.call(force.drag);
node.append("image")
.attr("class", function (d) { return "group-" + d.group })
.attr("xlink:href", function (d) { return d.icon })
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
});
{
"nodes": [
{
"group": 1,
"name": "Email",
"icon": "icon-envelope.png"
},
{
"group": 1,
"name": "IRC",
"icon": "icon-user.png"
},
{
"group": 1,
"name": "Android",
"icon": "icon-phone.png"
},
{
"group": 1,
"name": "Desktop",
"icon": "icon-desktop.png"
},
{
"group": 1,
"name": "Websockets",
"icon": "icon-websockets.png"
},
{
"group": 2,
"name": "remi",
"icon": "http://cdn.libravatar.org/avatar/33ddfefecc66c71eb7d6eb60503cf7f665d3bceb22e2f8e3af2704260a995ab2?s=16"
},
{
"group": 2,
"name": "spot",
"icon": "http://cdn.libravatar.org/avatar/1e232310ef80ec8b34b0bc216864efd0d837f419e6988997cda8e98a28be48dd?s=16"
},
{
"group": 2,
"name": "puiterwijk",
"icon": "http://cdn.libravatar.org/avatar/983782d075ab4e1fb02a7e7c7ca4d7096f6769bc9fe51b50e80eb012ddebc9ce?s=16"
},
{
"group": 2,
"name": "vicodan",
"icon": "http://cdn.libravatar.org/avatar/d16e1a49a16998d190f6bea5d0c36357d6e8bddc1427edb5ee277afabff16aee?s=16"
},
{
"group": 2,
"name": "pingou",
"icon": "http://cdn.libravatar.org/avatar/01fe73d687f4db328da1183f2a1b5b22962ca9d9c50f0728aafeac974856311c?s=16"
},
{
"group": 2,
"name": "mrunge",
"icon": "http://cdn.libravatar.org/avatar/c626775cb5584b1a49ea4a5cabb248e54e49c09185ebbf4fbce72265255956f0?s=16"
},
{
"group": 2,
"name": "orion",
"icon": "http://cdn.libravatar.org/avatar/b6fd449fcea49e2de27dacf226d46a01a0c8a44918672843e45bf5ac0bd0060b?s=16"
},
{
"group": 2,
"name": "cicku",
"icon": "http://cdn.libravatar.org/avatar/e04beb63c279ac05f99dd5ea1252e8d45b15611d1a9226bc42bda9fb31f24c75?s=16"
},
{
"group": 2,
"name": "rdieter",
"icon": "http://cdn.libravatar.org/avatar/f93140003949ff349627b7799edd42b927b057236ddd677b71257e0b974e6cb1?s=16"
},
{
"group": 2,
"name": "ralph",
"icon": "http://cdn.libravatar.org/avatar/9c9f7784935381befc302fe3c814f9136e7a33953d0318761669b8643f4df55c?s=16"
},
{
"group": 2,
"name": "pbrobinson",
"icon": "http://cdn.libravatar.org/avatar/861506b8dba9fe9ee12a83ce2cd7c6fff15091298f9f1a4e6149876490c53e9b?s=16"
},
{
"group": 2,
"name": "sgallagh",
"icon": "http://cdn.libravatar.org/avatar/0929fed032bd0a481ef74c46023fefe443f3d1b72dbe3efd293b25ed4fc843fd?s=16"
},
{
"group": 3,
"name": "Package Commits",
"icon": "https://apps.fedoraproject.org/img/icons/git-logo.png"
},
{
"group": 3,
"name": "Wiki Edits",
"icon": "https://fedoraproject.org/w/skins/common/images/mediawiki.png"
},
{
"group": 3,
"name": "Mailing List Messages",
"icon": "http://cloud.ohloh.net/attachments/37686/mailman_med.png"
},
{
"group": 3,
"name": "Fedora Hosted Events",
"icon": "http://www.edgewall.org/gfx/trac_bullet.png"
},
{
"group": 3,
"name": "Account Changes",
"icon": "https://admin.fedoraproject.org/accounts/static/theme/fas/images/account.png"
},
{
"group": 3,
"name": "Koji Builds",
"icon": "http://fedoraproject.org/w/uploads/2/20/Artwork_DesignService_koji-icon-48.png"
},
{
"group": 3,
"name": "Package Updates",
"icon": "https://admin.fedoraproject.org/updates/static/images/bodhi-icon-48.png"
},
{
"group": 3,
"name": "Package ACL Updates",
"icon": "https://apps.fedoraproject.org/packages/images/icons/package_128x128.png"
},
{
"group": 3,
"name": "Calendar Events",
"icon": "https://apps.fedoraproject.org/calendar/static/calendar.png"
}
],
"links": [
{
"source": 1,
"type": "output",
"target": 11
},
{
"source": 2,
"type": "output",
"target": 14
},
{
"source": 3,
"type": "output",
"target": 10
},
{
"source": 4,
"type": "output",
"target": 13
},
{
"source": 0,
"type": "output",
"target": 9
},
{
"source": 1,
"type": "output",
"target": 16
},
{
"source": 2,
"type": "output",
"target": 7
},
{
"source": 3,
"type": "output",
"target": 15
},
{
"source": 4,
"type": "output",
"target": 6
},
{
"source": 0,
"type": "output",
"target": 5
},
{
"source": 1,
"type": "output",
"target": 8
},
{
"source": 2,
"type": "output",
"target": 12
},
{
"source": 3,
"type": "output",
"target": 11
},
{
"source": 4,
"type": "output",
"target": 14
},
{
"source": 0,
"type": "output",
"target": 10
},
{
"source": 19,
"type": "input",
"target": 0
},
{
"source": 25,
"type": "input",
"target": 1
},
{
"source": 23,
"type": "input",
"target": 4
},
{
"source": 22,
"type": "input",
"target": 3
},
{
"source": 21,
"type": "input",
"target": 2
},
{
"source": 24,
"type": "input",
"target": 0
},
{
"source": 18,
"type": "input",
"target": 1
},
{
"source": 20,
"type": "input",
"target": 4
},
{
"source": 17,
"type": "input",
"target": 3
},
{
"source": 19,
"type": "input",
"target": 2
},
{
"source": 25,
"type": "input",
"target": 0
},
{
"source": 23,
"type": "input",
"target": 1
},
{
"source": 22,
"type": "input",
"target": 4
}
]
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Fedora Notifications Dynamic Logo</title>
<script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
<link href="directed.css" type="text/css" media="all" rel="stylesheet"/>
</head>
<body>
<script type="text/javascript" src="directed.js"></script>
</body>
</html>
import requests
import hashlib
import random
import json
import fedmsg.config
import fedmsg.meta
config = fedmsg.config.load_config()
fedmsg.meta.make_processors(**config)
N_OUTPUT = 15
N_INPUT = 13
N_USERS = 12
response = requests.get("https://badges.fedoraproject.org/leaderboard/json")
d = response.json()
usernames = [entry['nickname'] for entry in d['leaderboard']]
def libravatar(username):
template = "http://cdn.libravatar.org/avatar/{value}?s=16"
openid = "http://%s.id.fedoraproject.org/" % username
value = hashlib.sha256(openid).hexdigest()
return template.format(value=value)
# TODO these need icons
contexts = [
{
'name': ctx,
'group': 1,
'icon': icon,
} for ctx, icon in [
('Email', 'icon-envelope.png'),
('IRC', 'icon-user.png'),
('Android', 'icon-phone.png'),
('Desktop', 'icon-desktop.png'),
('Websockets', 'icon-websockets.png'),
]
]
no_avatar = ['ausil', 'patches', 'nb', 'kalev']
users = [
{
'name': username,
'group': 2,
'icon': libravatar(username),
} for username in usernames if username not in no_avatar]
sources = [
{
'name': proc.__obj__,
'icon': proc.__icon__,
'group': 3,
} for proc in fedmsg.meta.processors]
# TODO -- keep track of these to get more icons down the road.
# Clean out bogus ones.
for i in range(len(sources)):
if sources[i]['icon'] is None:
del sources[i]['icon']
# Furthermore
for i in reversed(range(len(sources))):
if 'icon' not in sources[i]:
sources.pop(i)
#sources = random.sample(sources, 10)
users = random.sample(users, N_USERS)
nodes = contexts + users + sources
def make_random_links_1(N):
context_indices = range(len(contexts))
user_indices = range(len(contexts), len(contexts) + len(users))
random.shuffle(context_indices)
random.shuffle(user_indices)
for i in range(N):
yield {
'source': context_indices[i % len(context_indices)],
'target': user_indices[i % len(user_indices)],
'type': 'output',
}
def make_random_links_2(N):
context_indices = range(len(contexts))
source_indices = range(len(contexts) + len(users),
len(contexts) + len(users) + len(sources))
random.shuffle(context_indices)
random.shuffle(source_indices)
for i in range(N):
yield {
'source': source_indices[i % len(source_indices)],
'target': context_indices[i % len(context_indices)],
'type': 'input',
}
links = list(make_random_links_1(N_OUTPUT)) +\
list(make_random_links_2(N_INPUT))
print json.dumps(dict(nodes=nodes, links=links), indent=2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment