This is going to be a bare bones chart, no fancy animations and whatnot. To that end, let's begin with the HTML and D3 import. You will see that in the HTML, I have also included a h1 tag, an SVG tag and a script tag.
<!DOCTYPE html>
<html>
<head>
<title>Quit Smoking!</title>
<style>
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<h1>The Chinups - Cigarettes Combo Chart</h1>
<svg>
</svg>
<script>
</script>
</body>
</html>
<html>
<head>
<title>Quit Smoking!</title>
<style>
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<h1>The Chinups - Cigarettes Combo Chart</h1>
<svg>
</svg>
<script>
</script>
</body>
</html>
In the CSS, we set a width and height for the SVG, and some specs for the h1 tag. The specs for the SVG are more pertinent to this tutorial.
<style>
svg
{
width: 800px;
height: 500px;
}
h1
{
font-family: verdana;
font-size: 16px;
}
</style>
svg
{
width: 800px;
height: 500px;
}
h1
{
font-family: verdana;
font-size: 16px;
}
</style>
That really is it for the HTML. From this point on, it will be mostly about the JavaScript. In the script tag, we have a comboChart object. Aside from that, we also make a call to d3's csv() method, passing in the name of the file, combo.csv, and a callback. We're going to be developing these two blocks somewhat concurrently.
<script>
let comboChart =
{
};
d3.csv("combo.csv", function(data)
{
});
</script>
let comboChart =
{
};
d3.csv("combo.csv", function(data)
{
});
</script>
In comboChart, we have properties cigData and chinupData, which are arrays. We then have maxUnits and dataPoints, which are integers with a default value of 1.
<script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
});
</script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
});
</script>
When parsing combo.csv, the dataPoints property is set to the length of data.
<script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
});
</script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
});
</script>
We then have a For loop to iterate through data. The figure in the Reps column gets pushed into the chinupData array. The figure in the Cigs column gets pushed into the cigData array.
<script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
}
});
</script>
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1
};
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
}
});
</script>
We then define maxReps. Using the max() method of d3, we want maxReps to contain the maximum value in chinupData. We then do the same for maxCigs, using it to contain the maximum value in cigData.
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
}
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
});
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
});
And then we set maxUnits to maxReps or maxCigs, whichever value is higher.
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
}
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
if (comboChart.maxUnits < maxReps) comboChart.maxUnits = maxReps;
if (comboChart.maxUnits < maxCigs) comboChart.maxUnits = maxCigs;
});
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
if (comboChart.maxUnits < maxReps) comboChart.maxUnits = maxReps;
if (comboChart.maxUnits < maxCigs) comboChart.maxUnits = maxCigs;
});
Finally, we call the drawCharts() method from the comboChart object.
d3.csv("combo.csv", function(data)
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
{
comboChart.dataPoints = data.length;
for (var i = 0; i < data.length; i++)
{
comboChart.chinupData.push(parseInt(data[i].Reps));
comboChart.cigData.push(parseInt(data[i].Cigs));
}
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
if (comboChart.maxUnits < maxReps) comboChart.maxUnits = maxReps;
if (comboChart.maxUnits < maxCigs) comboChart.maxUnits = maxCigs;
comboChart.drawCharts();
});
var maxReps = d3.max(comboChart.chinupData);
var maxCigs = d3.max(comboChart.cigData);
if (comboChart.maxUnits < maxReps) comboChart.maxUnits = maxReps;
if (comboChart.maxUnits < maxCigs) comboChart.maxUnits = maxCigs;
comboChart.drawCharts();
});
We've not created drawCharts() yet. Well, no time like the present! In it, use the select() method of d3 to get the sole SVG object, and set it to the variable svgChart. Then clear svgChart.
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1,
drawCharts: function()
{
var svgChart = d3.select("svg");
svgChart.html("");
}
}
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1,
drawCharts: function()
{
var svgChart = d3.select("svg");
svgChart.html("");
}
}
Next, run the drawAxes() method, passing in svgChart as an argument.
let comboChart =
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1,
drawCharts: function()
{
var svgChart = d3.select("svg");
svgChart.html("");
this.drawAxes(svgChart);
}
}
{
cigData: [],
chinupData: [],
maxUnits: 1,
dataPoints: 1,
drawCharts: function()
{
var svgChart = d3.select("svg");
svgChart.html("");
this.drawAxes(svgChart);
}
}
We'll want to create this method. It has a parameter, chart. In drawCharts(), chart was already passed when drawAxes() was called.
drawCharts: function()
{
var svgChart = d3.select("svg");
svgChart.html("");
this.drawAxes(svgChart);
},
drawAxes: function(chart)
{
}
{
var svgChart = d3.select("svg");
svgChart.html("");
this.drawAxes(svgChart);
},
drawAxes: function(chart)
{
}
We append one horizontal line and one vertical line. Bearing in mind that we have a 800 by 500 pixel square, and we want a 50 pixel buffer around the perimeter, this is how the x1, x2, y1 and y2 attributes are calculated. We use the CSS classes axesVertical and axesHorizontal.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
}
These two CSS classes described a thin grey line.
svg
{
width: 800px;
height: 500px;
}
h1
{
font-family: verdana;
font-size: 16px;
}
.axesVertical, .axesHorizontal
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
{
width: 800px;
height: 500px;
}
h1
{
font-family: verdana;
font-size: 16px;
}
.axesVertical, .axesHorizontal
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
Here be your starting lines.
We then want to determine the amount of pixels dedicated to each point plotted on the horizontal and vertical axes. Since we have already determined maxUnits (the highest value in the entire dataset) and dataPoints (the total number of data rows in the entire dataset), we can define pxPerUnit as available vertical space (500 - 50 - 50 = 400) divided by maxUnits and pxPerPoint as (800 - 50 - 50 = 700) divided by dataPoints. And since we only want whole numbers, we'll use the floor() method on these results.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
}
We then define scale as an empty array. We will populate scale with the y-positions we want on the vertical axis. Remember that we start from 450 and end with 50 because we're implementing a 50 pixel buffer on the maximum heigh of 500 pixels. And we do in in steps of pxPerUnit.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
}
And then we go through chart to append line tags that are styled using the CSS class scaleTick. These will be about 10 pixels in length. The data used will be the scale array, which we've already populated with the appropriated values.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
}
We'll add scaleTick to this CSS specification.
.scaleTick, .axesVertical, .axesHorizontal
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
And the ticks appear.
Then of course, we're going to need text. We will insert text tags, styled using the scaleText CSS class. The data used will be scale again, and the y attribute will depend on the current element of scale. The text itself, will reflect what the current index of scale is. And this, not-so-coincidentally, will be the value that the specific tick on the axis represents!
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
}
In the CSS, scaleText is defined like this.
.scaleTick, .axesVertical, .axesHorizontal
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
.scaleText
{
font: 8px verdana;
fill: rgba(100, 100, 100, 1);
text-anchor: end;
}
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
.scaleText
{
font: 8px verdana;
fill: rgba(100, 100, 100, 1);
text-anchor: end;
}
Here, the text appears. Note that your maximum value is 10!
Now we want to plot data points along the horizontal axes. To that end, we create the axes array. Then we populate it in a way similar to what we did for scale, instead using 750 and 50 as the end points in the For loop, and pxPerPoint as the decrementor.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
}
Then we insert a bunch of 10 pixel vertical lines, styled using axesTick, into the chart. The data used is axes.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
chart.selectAll("line.axesTick")
.data(axes)
.enter()
.append("line")
.attr("class", "axesTick")
.attr("x1", function(d)
{
return d + "px";
})
.attr("y1", "450px")
.attr("x2", function(d)
{
return d + "px";
})
.attr("y2", "460px");
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
chart.selectAll("line.axesTick")
.data(axes)
.enter()
.append("line")
.attr("class", "axesTick")
.attr("x1", function(d)
{
return d + "px";
})
.attr("y1", "450px")
.attr("x2", function(d)
{
return d + "px";
})
.attr("y2", "460px");
}
We'll add axesTick to this CSS specification.
.scaleTick, .axesTick, .axesVertical, .axesHorizontal
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
{
stroke: rgba(100, 100, 100, 1);
stroke-width: 1px;
}
And the horizontal axis is populated with ticks.
At the end of this method, we will run the methods drawLines() and drawBars(), passing in chart, pxPerUnit and pxPerPoint as arguments.
drawAxes: function(chart)
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
chart.selectAll("line.axesTick")
.data(axes)
.enter()
.append("line")
.attr("class", "axesTick")
.attr("x1", function(d)
{
return d + "px";
})
.attr("y1", "450px")
.attr("x2", function(d)
{
return d + "px";
})
.attr("y2", "460px");
this.drawBars(chart, pxPerUnit, pxPerPoint);
this.drawLines(chart, pxPerUnit, pxPerPoint);
}
{
chart
.append("line")
.attr("class", "axesVertical")
.attr("x1", "50px")
.attr("y1", "50px")
.attr("x2", "50px")
.attr("y2", "450px");
chart
.append("line")
.attr("class", "axesHorizontal")
.attr("x1", "50px")
.attr("y1", "450px")
.attr("x2", "750px")
.attr("y2", "450px");
var pxPerUnit = Math.floor(400 / this.maxUnits);
var pxPerPoint = Math.floor(700 / this.dataPoints);
var scale = [];
for (var i = 450; i >= 50; i -= pxPerUnit)
{
scale.push(i);
}
chart.selectAll("line.scaleTick")
.data(scale)
.enter()
.append("line")
.attr("class", "scaleTick")
.attr("x1", "40px")
.attr("y1", function(d)
{
return d + "px";
})
.attr("x2", "50px")
.attr("y2", function(d)
{
return d + "px";
});
chart.selectAll("text.scaleText")
.data(scale)
.enter()
.append("text")
.attr("class", "scaleText")
.attr("x", "30px")
.attr("y", function(d)
{
return d + "px";
})
.text(
function(d, i)
{
return i;
});
var axes = [];
for (var i = 750; i >= 50; i -= pxPerPoint)
{
axes.push(i);
}
chart.selectAll("line.axesTick")
.data(axes)
.enter()
.append("line")
.attr("class", "axesTick")
.attr("x1", function(d)
{
return d + "px";
})
.attr("y1", "450px")
.attr("x2", function(d)
{
return d + "px";
})
.attr("y2", "460px");
this.drawBars(chart, pxPerUnit, pxPerPoint);
this.drawLines(chart, pxPerUnit, pxPerPoint);
}
Of course, we will need to create these methods.
this.drawBars(chart, pxPerUnit, pxPerPoint);
this.drawLines(chart, pxPerUnit, pxPerPoint);
},
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
},
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
}
this.drawLines(chart, pxPerUnit, pxPerPoint);
},
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
},
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
}
Let's begin with drawLines(). We want to use the line portion of the combo chart to represent the smoking data. Thus, we declare cigData and set it to the value of the cigData array.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
},
{
var cigData = this.cigData;
},
Now, in chart, we want to append line tags. cigData is the dataset we will use. The CSS class used for this is lineChart.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart");
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart");
},
In the CSS, lineChart is a two pixel red line.
.scaleText
{
font: 8px verdana;
fill: rgba(100, 100, 100, 1);
text-anchor: end;
}
.lineChart
{
stroke: rgba(255, 0, 0, 1);
stroke-width: 2px;
}
{
font: 8px verdana;
fill: rgba(100, 100, 100, 1);
text-anchor: end;
}
.lineChart
{
stroke: rgba(255, 0, 0, 1);
stroke-width: 2px;
}
We set x1 like this. First, we define the variable val which is the index of the dataset, i. If it is currently not the first element in the dataset (i.e, i is greater than 0), then we decrement val.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
});
},
And then we set x1 to val multiplied by pxPerPoint, plus 50 for the buffer. In effect, if it's the first element in the dataset, x1 does pretty much nothing because it's at 0. If not, it uses the previous position in the dataset.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
});
},
x2, of course, is the current index, i, multipled by pxPerPoint and plus 50.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
});
},
For y1, we do something similar to what we did for x1. Except that we initially set val to the current value of the dataset instead of the index. And if it's not the first element in the dataset, we set val to the previous value of cigData.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
});
},
And then we set y1 by taking 450, which is position 0 on the vertical axis, and subtracting the product of val and pxPerUnit from it.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
return (450 - (val * pxPerUnit)) + "px";
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
return (450 - (val * pxPerUnit)) + "px";
});
},
And we set y2 similarly, except we just use d in a more straightforward way.
drawLines: function(chart, pxPerUnit, pxPerPoint)
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
return (450 - (val * pxPerUnit)) + "px";
})
.attr("y2", function(d)
{
return (450 - (d * pxPerUnit)) + "px";
});
},
{
var cigData = this.cigData;
chart.selectAll("line.lineChart")
.data(cigData)
.enter()
.append("line")
.attr("class", "lineChart")
.attr("x1", function(d, i)
{
var val = i;
if (i > 0)
{
val = val - 1;
}
return ((val * pxPerPoint) + 50) + "px";
})
.attr("x2", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y1", function(d, i)
{
var val = d;
if (i > 0)
{
val = cigData[i - 1];
}
return (450 - (val * pxPerUnit)) + "px";
})
.attr("y2", function(d)
{
return (450 - (d * pxPerUnit)) + "px";
});
},
You see that red line? That shows me starting the programme at 10 cigarettes a day, then moving my way down to 5 gradually over the next few months, and finally dropping to 0.
The next part, drawing the bar chart portion of the combo chart, is simple by comparison. We begin by declaring chinupData and setting it to the value of the chinupData array.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
}
{
var chinupData = this.chinupData;
}
In chart, we append rect tags that are styled using the CSS class barChart. The data used for this is chinupData.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart");
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart");
In the CSS, the barChart CSS class has an orange background.
.lineChart
{
stroke: rgba(255, 0, 0, 1);
stroke-width: 2px;
}
.barChart
{
fill: rgba(255, 200, 100, 1);
stroke-width: 0px;
}
{
stroke: rgba(255, 0, 0, 1);
stroke-width: 2px;
}
.barChart
{
fill: rgba(255, 200, 100, 1);
stroke-width: 0px;
}
Let's do the easy part first. Every bar has the same width - pxPerPoint pixels.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("width", pxPerPoint + "px");
}
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("width", pxPerPoint + "px");
}
The height depends on the value of the current element in chinupData, d. We multiply d by pxPerUnit to get the height.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
The x attribute depends on the index, i, of the current element. We multiply it by pxPerPoint and add 50 for the horizontal buffer.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("x", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("x", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
For y, we take 450 (which is position 0 on the vertical axis) and subtract from it the product of d and pxPerUnit. In essence, we subtract the height of the bar from 450 to get the starting y-position of the rect tag.
drawBars: function(chart, pxPerUnit, pxPerPoint)
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("x", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y", function(d)
{
return (450 - (d * pxPerUnit)) + "px";
})
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
{
var chinupData = this.chinupData;
chart.selectAll("rect.barChart")
.data(chinupData)
.enter()
.append("rect")
.attr("class", "barChart")
.attr("x", function(d, i)
{
return ((i * pxPerPoint) + 50) + "px";
})
.attr("y", function(d)
{
return (450 - (d * pxPerUnit)) + "px";
})
.attr("width", pxPerPoint + "px")
.attr("height", function(d)
{
return (d * pxPerUnit) + "px";
});
}
And there's the orange bar chart. You can see where I started off at 1 chinup, then shot up to 3 within a week, and how I gradually worked my way up from there.
There, all done!
This was simple in comparison to all the stuff we've gone through before with D3. That is a deliberate choice on my part - to give you the Combo Chart without throwing too much stuff in.Also - and yes, I just wanna brag here - I'm three days away from making it an entire year without a cigarette. Go, me!
Cig-nificantly yours,
T___T
T___T
No comments:
Post a Comment