Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Making Dashboards with Dc.js - Part 4: Style

, 20 Jan 2014
Rate this:
Please Sign up or sign in to vote.
Styling dc.js graphs

Introduction

In this part, we will focus more on style. We'll change the colors, the look and some StyleSheets for our graphs to give them a bit more individuality. We'll also cover a new type of graph and a little of how SVG works; so you can get familiar with how dc, and in turn, d3 is making the graphs.

Background 

You can find my other articles here:

Using the code

Since we've covered how to make basic graphs, I'm going to start with a basic setup. Our data is going to have a little more meat to it, so we can make things look a little more proper in the graph. We want to make a dashboard of the number of hits we get on our website again. Although, this time we're more concerned at looking at how we are doing each quarter.
var data = [
	{"quarter":"Q1","hits":0,"date":"01/01/2011"},
	{"quarter":"Q1","hits":0,"date":"01/15/2011"},
	{"quarter":"Q1","hits":0,"date":"02/01/2011"},
	{"quarter":"Q1","hits":0,"date":"02/15/2011"},
	{"quarter":"Q1","hits":0,"date":"03/01/2011"},
	{"quarter":"Q1","hits":0,"date":"03/15/2011"},
	{"quarter":"Q2","hits":0,"date":"04/01/2011"},
	{"quarter":"Q2","hits":0,"date":"04/15/2011"},
	{"quarter":"Q2","hits":0,"date":"05/01/2011"},
	{"quarter":"Q2","hits":0,"date":"05/15/2011"},
	{"quarter":"Q2","hits":0,"date":"06/01/2011"},
	{"quarter":"Q2","hits":0,"date":"06/15/2011"},
	{"quarter":"Q3","hits":0,"date":"07/01/2011"},
	{"quarter":"Q3","hits":0,"date":"07/15/2011"},
	{"quarter":"Q3","hits":0,"date":"08/01/2011"},
	{"quarter":"Q3","hits":0,"date":"08/15/2011"},
	{"quarter":"Q3","hits":0,"date":"09/01/2011"},
	{"quarter":"Q3","hits":0,"date":"09/15/2011"},
	{"quarter":"Q4","hits":12,"date":"10/01/2011"},
	{"quarter":"Q4","hits":13,"date":"10/15/2011"},
	{"quarter":"Q4","hits":16,"date":"11/01/2011"},
	{"quarter":"Q4","hits":15,"date":"11/15/2011"},
	{"quarter":"Q4","hits":12,"date":"12/01/2011"},
	{"quarter":"Q4","hits":10,"date":"12/15/2011"},

	{"quarter":"Q1","hits":25,"date":"01/01/2012"},
	{"quarter":"Q1","hits":27,"date":"01/15/2012"},
	{"quarter":"Q1","hits":28,"date":"02/01/2012"},
	{"quarter":"Q1","hits":26,"date":"02/15/2012"},
	{"quarter":"Q1","hits":29,"date":"03/01/2012"},
	{"quarter":"Q1","hits":24,"date":"03/15/2012"},
	{"quarter":"Q2","hits":36,"date":"04/01/2012"},
	{"quarter":"Q2","hits":33,"date":"04/15/2012"},
	{"quarter":"Q2","hits":35,"date":"05/01/2012"},
	{"quarter":"Q2","hits":35,"date":"05/15/2012"},
	{"quarter":"Q2","hits":39,"date":"06/01/2012"},
	{"quarter":"Q2","hits":34,"date":"06/15/2012"},
	{"quarter":"Q3","hits":41,"date":"07/01/2012"},
	{"quarter":"Q3","hits":45,"date":"07/15/2012"},
	{"quarter":"Q3","hits":40,"date":"08/01/2012"},
	{"quarter":"Q3","hits":42,"date":"08/15/2012"},
	{"quarter":"Q3","hits":47,"date":"09/01/2012"},
	{"quarter":"Q3","hits":43,"date":"09/15/2012"},
	{"quarter":"Q4","hits":55,"date":"10/01/2012"},
	{"quarter":"Q4","hits":57,"date":"10/15/2012"},
	{"quarter":"Q4","hits":54,"date":"11/01/2012"},
	{"quarter":"Q4","hits":53,"date":"11/15/2012"},
	{"quarter":"Q4","hits":51,"date":"12/01/2012"},
	{"quarter":"Q4","hits":50,"date":"12/15/2012"},

	{"quarter":"Q1","hits":32,"date":"01/01/2013"},
	{"quarter":"Q1","hits":36,"date":"01/15/2013"},
	{"quarter":"Q1","hits":34,"date":"02/01/2013"},
	{"quarter":"Q1","hits":31,"date":"02/15/2013"},
	{"quarter":"Q1","hits":33,"date":"03/01/2013"},
	{"quarter":"Q1","hits":36,"date":"03/15/2013"},
	{"quarter":"Q2","hits":45,"date":"04/01/2013"},
	{"quarter":"Q2","hits":40,"date":"04/15/2013"},
	{"quarter":"Q2","hits":42,"date":"05/01/2013"},
	{"quarter":"Q2","hits":49,"date":"05/15/2013"},
	{"quarter":"Q2","hits":44,"date":"06/01/2013"},
	{"quarter":"Q2","hits":42,"date":"06/15/2013"},
	{"quarter":"Q3","hits":58,"date":"07/01/2013"},
	{"quarter":"Q3","hits":53,"date":"07/15/2013"},
	{"quarter":"Q3","hits":58,"date":"08/01/2013"},
	{"quarter":"Q3","hits":52,"date":"08/15/2013"},
	{"quarter":"Q3","hits":54,"date":"09/01/2013"},
	{"quarter":"Q3","hits":58,"date":"09/15/2013"},
	{"quarter":"Q4","hits":65,"date":"10/01/2013"},
	{"quarter":"Q4","hits":63,"date":"10/15/2013"},
	{"quarter":"Q4","hits":66,"date":"11/01/2013"},
	{"quarter":"Q4","hits":64,"date":"11/15/2013"},
	{"quarter":"Q4","hits":68,"date":"12/01/2013"},
	{"quarter":"Q4","hits":63,"date":"12/15/2013"}
]; 

We're going to restructure our data again, so we can format the date. We need a year column, so we can filter on the year and a qtime column, which removes the year. This will allow us to layer several years on on a single x-axis.

var parseDate = d3.time.format("%m/%d/%Y").parse;
var parseDate2 = d3.time.format("%m/%d").parse;
data.forEach(function(d) {
	d.date = parseDate(d.date);
	d.qtime = parseDate2((d.date.getMonth()+1)+"/"+d.date.getDate());
        d.Year=d.date.getFullYear();
});
var ndx = crossfilter(data);

We'll start with a ring like our previous example to filter the years for comparison.  

/************
Year Ring
*************/
var yearRingChart   = dc.pieChart("#chart-ring-year");
var yearDim  = ndx.dimension(function(d) {return +d.Year;});
//var year_total = yearDim.group().reduceSum(function(d) {return d.http_200+d.http_302+d.http_404;});
var year_total = yearDim.group().reduceSum(function(d) {return d.hits;});
yearRingChart
    .width(150).height(150)
    .dimension(yearDim)
    .group(year_total)
    .innerRadius(30); 

To show off the different years, we're going to use a stacked line chart. Again, this is similar to our previous example. 

/************
Stacked Area Chart
*************/
	var hitslineChart  = dc.lineChart("#chart-line-hitsperday");
	var dateDim = ndx.dimension(function(d) {return d.qtime;});
	var hits = dateDim.group().reduceSum(function(d) {return d.hits;});
	var minDate = new Date("01/01/1900");
	var maxDate = new Date("12/30/1900");

var hits_2011=dateDim.group().reduceSum(function(d) {if (d.Year===2011) {return d.hits;}else{return 0;}});
var hits_2012=dateDim.group().reduceSum(function(d) {if (d.Year===2012) {return d.hits;}else{return 0;}});
var hits_2013=dateDim.group().reduceSum(function(d) {if (d.Year===2013) {return d.hits;}else{return 0;}});


hitslineChart
	.width(500).height(200)
	.dimension(dateDim)
        .group(hits_2011,"2011")
        .stack(hits_2012,"2012")
        .stack(hits_2013,"2013")   
        .renderArea(true)
	.x(d3.time.scale().domain([minDate,maxDate]))
        .elasticX(true)
        .brushOn(false)
        .legend(dc.legend().x(60).y(10).itemHeight(13).gap(5))
	.yAxisLabel("Hits per day")
    ;

Our dashboard now looks like this: 

   

Now that we have our base, we can start to spice it up. The first thing we'll update is the colors. We'll get rid of the different hues of blue. Most dashboards are kind of boring so we want our dashboard to stand out and grab the viewer's attention, and also make the different years easily distinguishable. We also was to sync up the yearly graph with the linechart. We can do this by adding the following attribute to both charts.

.ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])

Another problem is that we notice is the text 2011 doesn't show up in the pie chart because its slice is too thin. We still have the linechart's legend to help us; but if your data point is even smaller, it would be really hard to select the right one. Luckily Dc.js has an easy solution for this. The pie/donut chart's legend has the added feature of letting you click it instead of the arc itself.

We'll increase the width and height, open up the inner radius and put our legend inside so it doesn't take up too much room.

.width(200).height(200) 
.innerRadius(60) 
.legend(dc.legend().x(80).y(70).itemHeight(13).gap(5))

Since we've shrunk the inner radius, we'll get rid of the labels.

.renderLabel(false)

If we wanted to, we could also get rid of the titles:

.renderTitle(false)  

The chart is looking a bit better.   

However, we'd like to display a little more information. Early on in the year, we forcast a certain amount of hits per day. It would be nice to see how the actual traffic meets those targets. We've already done a stacked area chart. We could just add on the targets as another series, but we want to highlight the targets a little more with just a dotted line. To do this we would need another line chart overlayed on our existing chart. Again Dc.js has a built in solution for this. The composite chart is made just for this purpose.  

Let's break out our original chart and call it compose1. Compose1 will take with it the dimension, group, stacks, even the colors and render area setting.

var compose1=dc.lineChart(hitslineChart)
             .dimension(hits)
             .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
             .renderArea(true)
             .group(hits_2011, "2011")
             .stack(hits_2012,"2012")
             .stack(hits_2013,"2013"); 

Next, we'll create another chart which will contain our targets, one for each year. We'll leave it as a line chart which means no renderArea attribute and make it out of dashes to distinguish it even more. That way we can match up the color with the year. 

var compose2= dc.lineChart(hitslineChart)
                .dimension(dateDim)
                .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
                .group(target_2011,"2011 Target")
                .stack(target_2012,"2012 Target")
                .stack(target_2013,"2013 Target")
                .dashStyle([5,5]);

We want the target data to just be single lines, so we can do filters like we did for the previous data but this time hard code them to a single value instead of the actual hits.

var target_2011=dateDim.group().reduceSum(function(d) {if (d.Year===2011 ) {return 10;}else{return 0;}});
var target_2012=dateDim.group().reduceSum(function(d) {if (d.Year===2012 ) {return 20;}else{return 0;}});
var target_2013=dateDim.group().reduceSum(function(d) {if (d.Year===2013 ) {return 30;}else{return 0;}}); 

If we take a look at what's left of our original chart after changing its type, we see only the axis information and a new  attribute called compose where we reference the two new ones. You'll also notice that we've commented out the elasticX. This is an unknown attribute in composite charts which cause errors if left in. 

var hitslineChart  = dc.compositeChart("#chart-line-hitsperday"); 
hitslineChart
	.width(500).height(200)
	.x(d3.time.scale().domain([minDate,maxDate]))
        .brushOn(false)
        .legend(dc.legend().x(60).y(10).itemHeight(13).gap(5))
        .yAxisLabel("Hits Per Day")
        //.elasticX(true)
        .compose([compose1, compose2]);  

   

If you take a look at the x-axis on our graph, you'll notice that the months overlap. We solved this in our previous example by rotating the labels. Dc.js doesn't have a way to do a second level of labels; but showing individual months is probably too much data and would distract the viewer. After all, a dashboard is mostly used as a high level overview of the data. If we need the details, we can highlight an individual point to see the raw data. What we're going to do is split up the data into four individual charts. Each chart will hold one quarter's worth of data. We'll hide the y-axis and legends for the 2nd, 3rd and 4th quarter graphs and show only the quarter label for each of them. This will make it look like a single chart broken into four parts and limit the x-axis to only 4 labels.  

To make things readable, we'll start by renaming our div tag by appeanding -q1, then we'll add 3 more which will be our other quarters. Once all the graphs are ready, there is a large amount of margin space, so we'll overlap them so they look more like one chart. A better way might be to use CSS's z-index.

<div id="chart-line-hitsperday-q1" ></div>
<div id="chart-line-hitsperday-q2" style='margin-left:-45px'></div>
<div id="chart-line-hitsperday-q3" style='margin-left:-45px'></div>
<div id="chart-line-hitsperday-q4" style='margin-left:-45px'></div>

 For the most part we can keep our current filters that we currently have:

/************
Common Area Chart data
*************/

var dateDim = ndx.dimension(function(d) {return d.qtime;});
var hits = dateDim.group().reduceSum(function(d) {return d.hits;});

var hits_2011=dateDim.group().reduceSum(function(d) {if (d.Year===2011) {return d.hits;}else{return 0;}});
var hits_2012=dateDim.group().reduceSum(function(d) {if (d.Year===2012) {return d.hits;}else{return 0;}});
var hits_2013=dateDim.group().reduceSum(function(d) {if (d.Year===2013) {return d.hits;}else{return 0;}});

var target_2011=dateDim.group().reduceSum(function(d) {if (d.Year===2011 ) {return 10;}else{return 0;}});
var target_2012=dateDim.group().reduceSum(function(d) {if (d.Year===2012 ) {return 20;}else{return 0;}});
var target_2013=dateDim.group().reduceSum(function(d) {if (d.Year===2013 ) {return 30;}else{return 0;}});

 Our original chart will now become hitslineChart_q1 and reference its new div tag. Our min and max date will now show only a quarter's worth of data. Since we only have data on 1 and 15's, we set that as our new time range. We'll shrink down the size of the chart and make it a little taller, set it's axis label to Q1.

/************
Q1
*************/
var hitslineChart_q1  = dc.compositeChart("#chart-line-hitsperday-q1");
var minDate_q1 = new Date("01/01/1900");
var maxDate_q1 = new Date("03/15/1900");

var compose1 = dc.lineChart(hitslineChart_q1)
                .dimension(hits)
                .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
                .renderArea(true)
                .group(hits_2011, "2011")
                .stack(hits_2012,"2012")
                .stack(hits_2013,"2013");

var compose2= dc.lineChart(hitslineChart_q1)
                .dimension(dateDim)
                .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
                .group(target_2011,"2011 Target")
                .stack(target_2012,"2012 Target")
                .stack(target_2013,"2013 Target")
                .dashStyle([5,5]);

hitslineChart_q1
	.width(210).height(250)
	.x(d3.time.scale().domain([minDate_q1,maxDate_q1]))
        .brushOn(false)
        .legend(dc.legend().x(60).y(10).itemHeight(13).gap(5))
	.yAxisLabel("Hits Per Day")
        .xAxisLabel("Q1")
        .compose([compose1, compose2])
        .xAxis().ticks(0)
    ; 

To hide all the ticks, we can use .xAxis().ticks(0) which tells dc to show 0 ticks. Even if you do this, it will still look like 2 ticks are showing. There is no way in Dc to remove the ticks, but we still want to clean this up a little. 

We can do this by bypassing Dc and manipulating the svg element directly. If we open up our developer console in firefox and inspect the element.   

We can see inside our chart-line-hitsperday-q1 div tag is our svg element used to create the graph objects. Underneath, we can see the g element with both x and axis classes. This is obviously our x axis. It's always nice when developers name things that make sense. Then we see our svg path set to M0,6V0H118V6. This means move the pen to 0,6 and draw a line to 0,0 then horizontal to 118,0 then vertically 6,0. Those two moves by 6 are the little ticks. If we set the line to just M0,0H118, it will remove them. Using the renderlet we can select that path element and directly set it's d attribute. This code must come before the ticks attribute or it will error.

.renderlet(function (chart) {chart.selectAll("g.x path.domain").attr('d', 'M0,0H118')}) 

Not too bad. Next, we'll look at Q2, which is more of the same. The date ranges are slightly different to match the quarter and we've removed the legend.

/************
Q2
*************/
var hitslineChart_q2  = dc.compositeChart("#chart-line-hitsperday-q2");
var minDate_q2 = new Date("04/01/1900");
var maxDate_q2 = new Date("06/15/1900");

var compose1 = dc.lineChart(hitslineChart_q2)
                .dimension(hits)
                .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
                .renderArea(true)
                .group(hits_2011,"2011")
                .stack(hits_2012,"2012")
                .stack(hits_2013,"2013");

var compose2= dc.lineChart(hitslineChart_q2)
                .dimension(dateDim)
                .ordinalColors(["#56B2EA","#E064CD","#F8B700","#78CC00","#7B71C5"])
                .group(target_2011,"2011 Target")
                .stack(target_2012,"2012 Target")
                .stack(target_2013,"2013 Target")
                .dashStyle([5,5]);

hitslineChart_q2
	.width(200).height(250)
	.x(d3.time.scale().domain([minDate_q2,maxDate_q2]))
        .brushOn(false)
        .xAxisLabel("Q2")
        .compose([compose1, compose2])
        .renderlet(function (chart) {chart.selectAll("g.x path.domain").attr('d', 'M0,0H118')})  
        .xAxis().ticks(0)
    ;

The other two quarters are pretty much identical.

One last trick we have is to do is remove the y axis.

Again, there is no setting in Dc, so we have to improvise. Since SVG is apart of the DOM, we can manipulate it like any other element with CSS.

#chart-line-hitsperday-q2.dc-chart .axis.y,
#chart-line-hitsperday-q3.dc-chart .axis.y,
#chart-line-hitsperday-q4.dc-chart .axis.y
{opacity: 0.0;}

Sometimes the CSS properties are a little different than normal tags. Here we can also make it so the gray isn't as harsh when we select and deselect a pie slice.

.dc-chart g.deselected path {
	opacity: .5;
    fill-opacity: .5;
}

.dc-chart .pie-slice :hover {
    fill-opacity: .8;
}

.dc-chart .pie-slice.highlight {
    fill-opacity: .8;
}

 And here we are!

 

### Tip ###

If clicked around our previous example and this one, you may have noticed when we filter one of the years they collapse onto each other making it impossible to see values of the years that are still left. To get around this, you can hide the deselected series from the graph, so the elements below it will show properly. Here is some sample code I've done for the first quarter, so you can compare its behavior to the others in the last jsfiddle.

/************
JQuery updates
*************/

$('#chart-ring-year').on('click', function(){
    if ($("g.pie-slice._0").is(".deselected")){
		hitslineChart_q1.selectAll("g.stack._0").attr("display", "none")
		hitslineChart_q1.selectAll("g.dc-tooltip._0").attr("display", "none")
	}else{
		hitslineChart_q1.selectAll("g.stack._0").attr("display", null)
		hitslineChart_q1.selectAll("g.dc-tooltip._0").attr("display", null)
	}
	if ($("g.pie-slice._1").is(".deselected")){
		hitslineChart_q1.selectAll("g.stack._1").attr("display", "none")
		hitslineChart_q1.selectAll("g.dc-tooltip._1").attr("display", "none")
	}else{
		hitslineChart_q1.selectAll("g.stack._1").attr("display", null)
		hitslineChart_q1.selectAll("g.dc-tooltip._1").attr("display", null)
	}
	if ($("g.pie-slice._2").is(".deselected")){
		hitslineChart_q1.selectAll("g.stack._2").attr("display", "none")
		hitslineChart_q1.selectAll("g.dc-tooltip._2").attr("display", "none")
	}else{
		hitslineChart_q1.selectAll("g.stack._2").attr("display", null)
		hitslineChart_q1.selectAll("g.dc-tooltip._2").attr("display", null)
	} 
}); 

 

 ### Tip ###  

This is the end of fourth article, I hope it was useful and you've learned something new. I have some more ideas for a fifth article so stay tuned. 

History 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Member 3792090

United States United States
I'm a Principal Performance Engineer who uses VB.Net, HTML, CSS, etc. to write automation tools and reports.

Comments and Discussions

 
QuestionThe pie-chart legend code here doesn't seem to work. PinmemberMember 1088119512-Jun-14 10:03 
AnswerRe: The pie-chart legend code here doesn't seem to work. PinmemberMember 37920903-Jul-14 9:28 
Generalbig help PinmemberRedder Hat27-Apr-14 4:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 20 Jan 2014
Article Copyright 2014 by Member 3792090
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid