Thursday, September 15, 2016

Angry man figures out how to make a bar chart in D3 v4

Oh, hey! apparently D3.js is really amazing because

Modifying documents using the W3C DOM API is tedious: the method names are verbose, and the imperative approach requires manual iteration and bookkeeping of temporary state.

Yeah, sure. Whatever. I need to draw a bar chart. Great. Super boring. Whatever. Is there a tutorial for that?

Quick Google search on D3 bar chart tutorials

Oh! Look at that. Fucking marvellous. A tutorial specifically about bar charts. Made my day. Wait. WTF is this? sideways barcharts? Who in their right mind does that? Useless. Oh. It's in three parts. skips to the end Cool. I'll copy/paste this into a browser and see what's up...

d3.scale is undefined

What? Code that doesn't work? What the fuck is wrong with these people? And why are they loading in a tsv? I'm getting my data from a server, numbnuts. Show me how to do it with json.

Heads over to the tutorial section of the official documentation

Tutorials may not be up-to-date with the latest version 4.0 of D3

Just great. No notes to tell me which work with the current version of the software. Fuckin A. Guess I'll write my own.

This is the data I'm working with. Pretty simple stuff.

data.weekday_visits = 
{
    'Monday':132,
    'Tuesday':140,
    'Wednesday':159,
    'Thursday':129,
    'Friday':158,
    'Saturday':132,
    'Sunday':150,
}

Seems pretty reasonable, right? The first thing I need to do is change it suit D3 almighty.

var weekdayVisits = [];
var days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var maxVisits = 0;

for(var i = 0; i < days.length; i++)
{
    if(data.weekday_visits[days[i]] > maxVisits)
    {
        maxVisits = data.weekday_visits[days[i]];
    }

    weekdayVisits.push({'day': days[i], 'value': data.weekday_visits[days[i]]});
}

That's right. Now it's an array of dictionaries. Amazing.

Now I need to figure out how to draw a fucking chart. What does D3 even do, beyond the marketobabble they spout on the homepage? Apparently it can manipulate SVG into a bar chart, so the out of date tutorial told me.

<svg id="weekdayChart" width="400" height="400"></svg>

There, least I can do. So. Now what? Let's try drawing an axis. Should be easy, right? Good fucking luck. I've never used this library in my life. It's going to be a nightmare.

var chart = document.getElementById('weekdayChart');
var yAxis = d3.axisLeft(maxVisits); //remember maxVisits from above? I bet you thought I was crazy. Yep, but also we need this. This is for the "scale" parameter
yAxis(chart); //That's all I have to do to add an axis to a chart? Nice!

Let's give that a test...

TypeError: n.domain is not a function

Bollocks. What is n? How do I set its domain? Fucksake. What does the API say about axis domains? Fuck all. Great. Thanks a million for this really easy to use library. Of course searching brings up everything for version 3 and nothing for version 4 so that's as good as punching myself in the balls for half an hour. OK, so the out of date tutorial says something like: y.domain(some function) It's worth a shot.

yAxis.domain(function(d){ return d.value; });

Aaaaand eval...

TypeError: yAxis.domain is not a function

Well, at least this is consistent with the API. What next? OK, what if scales are a type. Maybe that's what they mean in the API. OK, clicking on a few links seems to confirm that, although it's not clear what scale I should use. I'm guessing Linear, what's the documentation say? Ah! They mention domains! Finally. Also something called ranges, which also appeared in the out of date tutorial. I'm sure this will fuck me up, too.

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]));

A NOPE!

TypeError: g.selectAll is not a function

More mysterious error messages about objects I know nothing about. SO HELPFUL! OK, OK. I'll stop using the .min version.

TypeError: selection.selectAll is not a function

Oh yeah. So much more helpful now. I haven't selected anything, so I have no idea what that's about. OK, I'll dig around in the code like a monkey scrounging for shit. Apparently something called context needs to have a method called selectAll or have a member called selection. I'll change things up a bit then.

var chart = d3.select('#weekdayChart');

Well, now it's running, but it doesn't... wait... what's that black spot? It's not a dead pixel! I think we've got something. I guess I need to give the scale a range now, so it'll stop being too tiny. I'll take my cue from the out of date tutorial (why am I doing this to myself?)

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([400, 0]));

Well hey! Look at that. I got a black line. Maybe I can do the same for the x axis.

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([0, 400]));
var xAxis = d3.axisBottom(d3.scaleBand().domain(days).range([0, 400]));

OK. The x axis looks a little dumb, but it's on the way to looking right, but now my yaxis is tiny again, or maybe completly invisible. Why the fuck has that happened?

Looking over the out of date tutorial, apparently I need a g for each axis. Fine.

var yAxisHolder = chart.append('g');
var xAxisHolder = chart.append('g');

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([0, 400]));
var xAxis = d3.axisBottom(d3.scaleBand().domain(days).range([0,400]));

yAxis(yAxisHolder);
xAxis(xAxisHolder);

OK. Now the y axis is back, buuuuuuuuuuuut the x axis is at the top. What the fuck? It's called axisBottom, so why's it at the top? Gahhhhhh! Looking at the out of date tutorial, the x axis holder needs to be translated to the bottom of the SVG. Great. But not completely to the bottom, because that hides the labels. Also, that means I need to change the range of the y axis to match the translation.

var xAxisHolder = chart.append('g').attr("transform", "translate(0," + 380 + ")");

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([0, 380]));

It turns out the whole axisBottom vs axisTop determines whether the labels will be above or below the line. That is actually mentioned in the API, so fine, but also, that's a terrible name. Of course, adding numbers to the y axis means more fiddling with where everything is, but at least it's easy to add the numbers (see the ticks at the end of the axis declaration).

var yAxisHolder = chart.append('g').attr("transform", "translate(30,10)");;
var xAxisHolder = chart.append('g').attr("transform", "translate(30,390)");

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([0, 380])).ticks();
var xAxis = d3.axisBottom(d3.scaleBand().domain(days).range([0,370]));

And I'll mage the SVG bigger so it will all fit.

<svg id="weekdayChart" width="400" height="410"></svg>

Huh. The number are going from 0 at the top. Why? I think I know this. This is the range thing. I have to put the numbers backwards. That's in the out of date tutorial.

var yAxis = d3.axisLeft(d3.scaleLinear().domain([0, maxVisits]).range([380,0])).ticks();

Yeah, that's fixed it.

OK. Now I want some bars. From the out of date tutorial, it looks like I want to add rect objects to the chart. This might actually work... doesn't hold breath

chart.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return xScale(d.day); })
    .attr("y", function(d) { return yscale(d.value); })
    .attr("height", function(d) { return 390 - yScale(d.value); })
    .attr("width", xScale.rangeBand());

Now we get the error:

TypeError: xScale.rangeBand is not a function

The API seems to suggest that bandwidth is the correct function name.

chart.selectAll(".bar")
    .data(weekdayVisits)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return xScale(d.day); })
    .attr("y", function(d) { return yScale(d.value); })
    .attr("height", function(d) { return 390 - yScale(d.value); })
    .attr("width", xScale.bandwidth());

Well, the error is gone, and I do see some bars but they are way too big, and off centre. Let me just hack at these position values...

chart.selectAll(".bar")
    .data(weekdayVisits)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return 40 + xScale(d.day); })
    .attr("y", function(d) { return 10 + yScale(d.value); })
    .attr("height", function(d) { return 380 - yScale(d.value); })
    .attr("width", xScale.bandwidth()-20);

Amazing. Now I have a bar chart. It only took two days of hacking to get here. Thanks to Mike Bostock for the out of date tutorial, and to the D3.js team for the API. Both gave help like panning for gold in a river. A lot of grit but some nuggets are in there if you've got two days to spare.

See below for the full code:

HTML

<svg id="weekdayChart" width="400" height="410"></svg>
<script type="text/javascript" src="d3.js"></script>
<script type="text/javascript" src="blogpost.js"></script>

CSS

.bar { fill: steelblue; }

JavaScript

data.weekday_visits = 
{
    'Monday':132,
    'Tuesday':140,
    'Wednesday':159,
    'Thursday':129,
    'Friday':158,
    'Saturday':132,
    'Sunday':150,
}

var weekdayVisits = [];

var days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];

var maxVisits = 0;

for(var i = 0; i < days.length; i++)
{
    if(data.weekday_visits[days[i]] > maxVisits)
    {
 maxVisits = data.weekday_visits[days[i]];
    }

    weekdayVisits.push({'day': days[i], 'value': data.weekday_visits[days[i]]});
}

var chart = d3.select('#weekdayChart');

var yAxisHolder = chart.append('g').attr("transform", "translate(30,10)");;
var xAxisHolder = chart.append('g').attr("transform", "translate(30,390)");

var yScale = d3.scaleLinear().domain([0, maxVisits]).range([380,0]);
var xScale = d3.scaleBand().domain(days).range([0,370]);

var yAxis = d3.axisLeft(yScale).ticks();
var xAxis = d3.axisBottom(xScale);

yAxis(yAxisHolder);
xAxis(xAxisHolder);

chart.selectAll(".bar")
    .data(weekdayVisits)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return 40 + xScale(d.day); })
    .attr("y", function(d) { return 10 + yScale(d.value); })
    .attr("height", function(d) { return 380 - yScale(d.value); })
    .attr("width", xScale.bandwidth()-20);

SVG output

020406080100120140MondayTuesdayWednesdayThursdayFridaySaturdaySunday

Sunday, January 24, 2016

Moral Objectivism

A friend of mine asked me to write about the existence of an objective morality. He said I had done something about it for my MSc. but, I can't remember it, that was 10 years ago. This is a new take, I guess.

I am an atheist, but I will try to address my understanding from both "their is a supernatural being that knows how everyone should behave" and the atheist standpoints.

There is a being that judges humans good or bad

Let's assume that one of the religions that says there is a being that knows right from wrong, e.g. Santa Claus, is right. Whatever that being says is the ultimate moral truth is just that.

So then that would mean there are objective morals.

OK, but since the being is intangible, and not everyone believes in that being, how can a human know what is and isn't moral.

Those who have faith say that their being (or beings) has instructed them in the way. There is more than one faith, so which is right? Also, faith comes from within, and therefore is subjective.

For some reason humans have to decide which of the beings is correct by themselves. Thus, from a human standpoint, morals must be subjective, because humans cannot objectively know the right way from the wrong way, because if that could be shown then we would all know if what we were doing was objectively right or wrong.  We could all agree about it, so we would know if someone was "bad" or not. Since we don't all agree about what is right or wrong, bad or good, then the true path isn't self evident.

Atheist are right

Let's assume there is no being that has moral oversight of the universe. So, then morality must be subjective. Each person has their own idea of right and wrong.

If that is the case then why do we have laws? Why do we have power structures? Why are there any rules at all in society?

Two reasons: to aid in coöperation and to reïnforce the power structure.

In the society of the UK as I am writing this, if I want something somebody else owns:
  • I can buy it, if they are willing to sell it
  • I can be given it, if they are willing to give it away
  • I can steal it.
We class the first two as morally correct and the second as morally incorrect. Except when we don't. If the thing has come into the possession of the other person by immoral means, e.g. if they stole it, then my buying or receiving it is deemed immoral. If the possessor stole the object and I steal it and give it back to the original owner, I am deemed to have behaved morally.

In the context of stealing, it is seen as morally incorrect because it reduces the likelihood of coöperation in the future (people protect their possessions more and don't share as much), and also because people with useful possession have power over those who need to use them, and so stealing breaks the power structure.

(This is also why gift giving to a stranger is seen as weaker than selling things to them, because without an exchange of money, the power structure is changed, so the person who has given is perceived as weaker and weakness is perceived as bad.)

So are increasing coöperation and reïnforcing power structures moral absolutes? Should we always seek to do these things?


If being morally correct is a choice, then no.


Sometimes increasing coöperation will change the current power structure. Sometimes enforcing the current power structure will decrease coöperation.

Let's say, for example, you want to increase the coöperation of environmentalists with oil prospectors.

Environmentalists (E) believe oil prospectors (OP) are wrong because E believe that the results of the OP will damage the environment, which is anti-coöperation, as it hurts lots of people, and will result in a complete collapse of the current power systems.

OP believe that E are wrong because OP's results keeps the current power systems running and therefore is in maximum coöperation.

Forcing one to coöperate with the other will cause a change in the power structure, because they will both have to give way, and so lose power.

Humans have very low predictive power. We struggle to accurately predict the repercussions of anything we do further ahead than a year, and most of the time we don't even try for further than a moment.

That's natural, due to how many moving parts our universe has.

Neither OP nor E can be sure about the long term outcome of their actions, so their moral stances are both relative to their understandings of the situation.

Conclusion

In the grand scheme of things, due to the human race's lack of understanding of the universe, moral decision is relative, as we don't know if the universe is for anything, so cannot know if our actions improve or impede the chance of universal success.

If the universe isn't for anything, then morals are a personal thing.

If the universe is under the moral purview of some being or beings then absolute morals are their concern, but not something we can divine.