Created
September 12, 2014 13:38
-
-
Save evandana/965bd1a6e49693db793d to your computer and use it in GitHub Desktop.
D3 Grid of Bar Charts: Sortable (one bar per cell)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: add description of this library | |
// TODO: add README.md | |
// TODO: don't allow any hovering until the entire chart is drawn and done animating | |
var D3Chart = function (options) { | |
// options not yet needed | |
// ---------------------------------------------------- | |
// get data | |
// ---------------------------------------------------- | |
var getData = function (options) { | |
// hook up options to local vars and set defaults | |
var data = options.data || {}, | |
columnInfo = options.columnInfo || {}, | |
chartParams = options.chartParams || {}, | |
metaDataParams = options.metaDataParams || {}; | |
var preprocessData = function(data) { | |
// if the actual data is contained in a data object, then just get the actual data | |
// currently anything other than the data is ignored | |
// accepts "data = {data: data, ... }"" and "data = data" | |
if (! $.isArray(data)) { | |
data = data.data; | |
} | |
// convert data to numbers | |
$.each(data, function(i, row) { | |
$.each(columnInfo, function(j, val) { | |
// cast value as number | |
var val = +row[columnInfo[j].field]; | |
data[i][columnInfo[j].field] = val; | |
}); | |
}); | |
return data; | |
}; | |
var metaData = function(options) { | |
// hook up options to local vars and set defaults | |
var data = options.data || {}; | |
var rowKeys = [], | |
colKeys = [], | |
sortOrder = 0; // asc|dsc | |
var getRowKeys = function(data) { | |
// if you already have a unique identifier, just return an array of these values in that order | |
// keys should adhere to the rules of CSS classes | |
// if both are null, then create | |
// TODO: allow a parameter to be passed in to choose one column as the unique key | |
// TODO: warn if a column does not have unique values | |
// TODO: check if "key" is used as a property of the object, if so use a different property (e.g. "uniqueKey") | |
if (rowKeys.length === 0 && !!data) { | |
$.each(data, function(i) { | |
var key = Math.floor(Math.random() * 1000000000000); | |
data[i].key = key; // TODO: unexpected mutation is not ideal | |
rowKeys.push(key); | |
}); | |
} | |
return rowKeys; | |
}; | |
var getColKeys = function(data) { | |
// make col keys from metaData columnInfo[i].key | |
colKeys = $.map(columnInfo, function(val) { | |
return val.field; | |
}); | |
return colKeys; | |
}; | |
var setRowKeys = function(newRowKeysArray) { | |
rowKeys = newRowKeysArray; | |
}; | |
// in theory there could be more options, but i can't think of what they would be | |
var sortOrderOptions = [-1, 1]; // -1:ascending, 1:descending | |
var getSortOrder = function () { | |
return sortOrder; | |
} | |
var incrementSortOrder = function (sortType) { | |
// the returned value is used in a comparison function during a sort (b - a) | |
// negative value switches (b - a) to (a - b) | |
switch (sortType) { // 0:off, 1:asc only, 2:dsc only, 3:asc|dcs toggle | |
case 0: | |
sortOrder = 0; | |
break; | |
case 1: | |
sortOrder = sortOrderOptions[0]; // -1 | |
break; | |
case 2: | |
sortOrder = sortOrderOptions[1]; // 1 | |
break; | |
case 3: | |
// uses modulus to wrap around the sortOrderArray index | |
sortOrder = sortOrderOptions[(sortOrderOptions.indexOf(sortOrder) + 1) % (sortOrderOptions.length)]; | |
break; | |
} | |
return sortOrder; | |
} | |
return { | |
// make private methods and vars publically available | |
columnInfo: columnInfo, | |
rowKeys: getRowKeys(data), // calculated on chart draw, not necessary to be calculated again... right? | |
colKeys: getColKeys(data), // calculated on chart draw, not necessary to be calculated again... right? | |
getSortOrder: getSortOrder, | |
incrementSortOrder: incrementSortOrder, | |
setRowKeys: setRowKeys | |
}; | |
}; | |
// preprocess data | |
data = preprocessData(data); | |
// create metaData from preprocessed data | |
var metaData = metaData({data: data, chartParams: chartParams}); | |
return {data: data, metaData: metaData}; | |
}; | |
// ---------------------------------------------------- | |
// merge input params with defaults (defaults get overridden if conflict exists) | |
// ---------------------------------------------------- | |
var mergeStaticParams = function (options) { | |
// hook up options to local vars and set defaults | |
var params = options.params || {}; | |
// define defaults, to be over ridden by incoming params | |
// TODO: not all of these properties are being used | |
// TODO: how many of these can be set with CSS instead and removed from here? | |
var defaultParams = { | |
sort: { | |
sortType: 3, // 0:off, 1:asc only, 2:dsc only, 3:asc|dcs toggle | |
animate: true, | |
duration: 300, // milliseconds | |
}, | |
header: { | |
height: 20 | |
}, | |
footer: { | |
height: 30, | |
marginY: .2, | |
paddingY: .2 | |
}, | |
margin: { | |
left: 0, | |
top: 0, | |
right: 0, | |
bottom: 0 | |
}, | |
cell: { | |
paddingY: 1, // TODO: causes error for zeroLine when this is 0 // if this is too big it causes problems | |
paddingX: 1, // TODO: causes error for zeroLine when this is 0 // if this is too big it causes problems | |
marginY: .5, // if this is too big it causes problems | |
marginX: .5, // if this is too big it causes problems | |
color: 'url(#gradient)', | |
hover: { | |
color: '#cccccc' | |
}, | |
stroke: '#bbbbbb', | |
strokeWidth: 1, | |
minWidth: .5 | |
}, | |
dataBar: { | |
width: 1, // width set dynamically | |
color: { | |
pos: '#4682B4', | |
neg: '#FFA500', | |
}, | |
stroke: '#ccf', | |
hover: { | |
pos: '#B1DCFE', | |
neg: '#FED280' | |
}, | |
minWidth: 1, | |
strokeWidth: 1, | |
animate: true, | |
duration: 500 | |
}, | |
zeroLine: { | |
strokeWidth: .5, | |
strokeColor: "black", | |
delay: 300, // entrance; should roughly match dataBar duration | |
duration: 300 // entrance | |
}, | |
marginLine: { | |
strokeWidth: .2, | |
strokeColor: "gray", | |
delay: 300, // entrance; should roughly match dataBar duration | |
duration: 300 // entrance | |
}, | |
color: { | |
fill: '#eeeeee' | |
} | |
}; | |
// merge params into defaultParams (defaults get overridden if conflicts arise) | |
params = $.extend(defaultParams, params); | |
return params; | |
}; | |
// ---------------------------------------------------- | |
// create and merge dynamic params with defaults (defaults get overridden if conflict exists) | |
// ---------------------------------------------------- | |
var mergeDynamicParams = function (options) { | |
// hook up options to local vars and set defaults | |
var params = options.params || {}, | |
el = options.el || '', | |
data = options.data || {}, | |
metaData = options.metaData || {}; | |
// immediately append "el" so it can be used as "params.*" consistently everywhere | |
params.el = { | |
width: $(el).width(), | |
height: $(el).height() | |
}; | |
// TODO: use d3.scale().ordinal() instead of this... | |
// dataScale for yAxis | |
var yScale = d3.scale.ordinal() | |
.domain(metaData.rowKeys) | |
.rangeRoundBands([0, params.el.height - params.header.height - params.footer.height], 0, 0); | |
// .rangeRoundBands([0, params.cell.height], .2); | |
// calculate dynamic params | |
var dynamicParams = { | |
chart: { // entire drawing | |
height: params.el.height, | |
width: params.el.width | |
}, | |
grid: { // the grid (no header) | |
height: params.el.height - params.header.height - params.footer.height, | |
width: params.el.width | |
}, | |
row: { | |
height: yScale.rangeBand() | |
}, | |
cell: { | |
width: (function () { | |
// adding Math.max sets the minimum size for each bar (even though it will not be to scale) | |
// TODO: abstract first param in Math.max to a common param | |
// TODO: make .5 a parameter, this makes the minimum val visible (in case it is otherwise 0) | |
return Math.max( params.cell.minWidth, (params.el.width / metaData.colKeys.length) - params.cell.paddingX * 2 ); | |
}()), | |
x: params.cell.marginX, // relative to cell start | |
y: params.cell.marginY, // relative to row | |
height: yScale.rangeBand() - params.cell.marginY * 2 | |
}, | |
dataBar: { | |
height: yScale.rangeBand() - (params.cell.paddingY * 2) - (params.cell.marginY * 2), | |
y: params.cell.marginY + params.cell.paddingY// relative to cell start | |
//params.cell.paddingY/2 + params.dataBar.strokeWidth + (params.cell.height - params.dataBar.height) / 2 | |
} | |
}; | |
// merge dynamicParams into params (params get overridden by dynamicParams if conflicts arise) | |
params.chart = $.extend(params.chart, dynamicParams.chart); | |
params.grid = $.extend(params.grid, dynamicParams.grid); | |
params.row = $.extend(params.row, dynamicParams.row); | |
params.cell = $.extend(params.cell, dynamicParams.cell); | |
params.dataBar = $.extend(params.dataBar, dynamicParams.dataBar); | |
return params; | |
} | |
// ---------------------------------------------------- | |
// draw chart | |
// ---------------------------------------------------- | |
var drawChart = function (options) { | |
// hook up options to local vars and set defaults | |
var data = options.data || {}, | |
metaData = options.metaData || {}, | |
el = options.el || '', | |
params = options.params || {}; | |
// ---------------------------------------------------- | |
// set up vars | |
// ---------------------------------------------------- | |
// used for adjusting the domain/range of data for each column | |
// TODO: move this to metaData | |
// TODO: does D3 have a method for doing this more simply? | |
var getMinMaxByProperty = function (data, metaData) { | |
var minMax = {}; | |
$.each(metaData.colKeys, function (i, colKey) { | |
minMax[colKey] = { | |
min: Math.min(0, d3.min(data, function(d) { return d[colKey]; }) ), | |
max: d3.max(data, function(d) { return d[colKey]; }) | |
}; | |
}); | |
return minMax; | |
}; | |
var minMaxByProperty = getMinMaxByProperty(data, metaData); | |
// creates a dataScale function for each column | |
// dataScale function returns the scaled value based on the given input | |
// this complexity is only used for xAxis | |
// TODO: move this to metaData | |
var getDataScales = function (data, metaData) { | |
var scaleByProperty = {}; | |
$.each(metaData.colKeys, function (i, colKey) { | |
scaleByProperty[colKey] = d3.scale.linear(colKey) | |
.domain( | |
[ | |
Math.min( 0, d3.min(data.map(function(d) { return d[colKey]; })) ), // draws the 0 mark even if chart has only values above 0 | |
d3.max(data.map(function(d) { return d[colKey]; })) | |
] | |
) | |
// the first array element and the value subtracted from the second are what determine the padding on the outside of each column | |
// TODO: abstract this "5" and put it up in the properties | |
.range([15, params.cell.width - 15 ]); | |
}); | |
return scaleByProperty; | |
}; | |
var scaleByProperty = getDataScales(data, metaData); | |
// ---------------------------------------------------- | |
// Create SVG | |
// ---------------------------------------------------- | |
var svg = d3.select(el).append('svg') | |
.attr('width', params.chart.width) | |
.attr('height', params.chart.height) | |
// TODO: why do we have this and is it right? | |
.attr('preserveAspectRatio', 'xMidYMid'); | |
// ---------------------------------------------------- | |
// Define gradients | |
// ---------------------------------------------------- | |
// define gradients | |
// TODO: name this more generically | |
// TODO: put these values in params | |
var gradient = svg.append("svg:defs") | |
.append("svg:linearGradient") | |
.attr("id", "gradient") | |
.attr("x1", "0%") | |
.attr("x2", "100%") | |
// .attr("y1", "100%") | |
// .attr("y2", "100%") | |
.attr("spreadMethod", "pad"); | |
gradient.append("svg:stop") | |
.attr("offset", "0%") | |
.attr("stop-color", "#ddd") | |
.attr("stop-opacity", 1); | |
gradient.append("svg:stop") | |
.attr("offset", "100%") | |
.attr("stop-color", "#eee") | |
.attr("stop-opacity", 1); | |
var gradient2 = svg.append("svg:defs") | |
.append("svg:linearGradient") | |
.attr("id", "gradient") | |
.attr("x1", "0%") | |
.attr("x2", "100%") | |
// .attr("y1", "100%") | |
// .attr("y2", "100%") | |
.attr("spreadMethod", "pad"); | |
gradient2.append("svg:stop") | |
.attr("offset", "0%") | |
.attr("stop-color", "#ccc") | |
.attr("stop-opacity", 1); | |
gradient2.append("svg:stop") | |
.attr("offset", "100%") | |
.attr("stop-color", "#ddd") | |
.attr("stop-opacity", 1); | |
// ---------------------------------------------------- | |
// Render the chart | |
// ---------------------------------------------------- | |
var grid = svg | |
// Wrapper group that will be translated by left and top margin | |
.append('g') | |
.attr('class', 'grid') | |
.attr('transform', 'translate(' + params.margin.left + ', ' + params.margin.top + ')') | |
// a group for every row | |
.selectAll('g') | |
.data(data) | |
.enter(); | |
var rows = grid | |
.append('g') | |
.attr('class', function(d) { | |
return 'row row-' + d.key; | |
}) | |
.attr('transform', function(d) { | |
var yvalue = metaData.rowKeys.indexOf(d.key); | |
var y = yvalue * (params.row.height); | |
return 'translate(0, ' + y + ')'; | |
}) | |
// a group for every column | |
.selectAll('g') | |
.data(metaData.columnInfo) | |
.enter() | |
.append('g') | |
.attr('class', function(d) { | |
return 'cell col-' + d.field; | |
}) | |
.attr('transform', function(d) { | |
var xvalue = metaData.colKeys.indexOf(d.field); | |
var x = xvalue * (params.cell.width + params.cell.x * 2); | |
return 'translate(' + x + ' 0)'; | |
}); | |
// draw cell backgrounds | |
var cellBackgrounds = rows | |
.append('rect') | |
.attr('class', 'cellBackground') | |
.attr('x', params.cell.x) | |
.attr('y', params.cell.y) | |
.attr('width', params.cell.width) | |
.attr('height', params.cell.height) | |
.style('fill', params.cell.color); | |
// Draw dataBars | |
rows | |
.data(function (d) { | |
return $.map(metaData.colKeys, function (colKey) { | |
return { | |
value: d[colKey], | |
field: colKey | |
}; | |
}); | |
}) | |
.append('rect') | |
.attr('class', function(d) { | |
return d.value >= 0 ? 'dataBar pos' : 'dataBar neg' | |
}) | |
.attr('x', function (d) { | |
var xvalue = metaData.colKeys.indexOf(d.field), | |
x; | |
// if your min value per range is below zero or above zero, calculate the "0 line" differently | |
if (minMaxByProperty[d.field].min >= 0) { | |
x = Math.abs(scaleByProperty[d.field](Math.max(0, minMaxByProperty[d.field].min))); | |
} else { | |
x = Math.abs(scaleByProperty[d.field](Math.min(0, d.value))); | |
} | |
return x; | |
}) | |
.attr('y', function () { | |
return params.dataBar.y; | |
}) | |
.attr('height', params.dataBar.height) | |
.attr('fill', function(d) { | |
return d.value < 0 ? params.dataBar.color.neg : params.dataBar.color.pos; | |
}) | |
.transition() | |
.duration(function () { | |
if (params.dataBar.animate) { | |
return params.dataBar.duration; | |
} else { | |
return 0; | |
}; | |
}) | |
.attr('width', function(d) { | |
var width = Math.abs(scaleByProperty[d.field](d.value) - scaleByProperty[d.field](Math.max(0, minMaxByProperty[d.field].min))); | |
// adding Math.max sets the minimum size for each bar (even though it will not be to scale) | |
// TODO: abstract first param in Math.max to a common param | |
return Math.max( params.dataBar.minWidth, width); | |
}); | |
// ---------------------------------------------------- | |
// Render the headers and text | |
// ---------------------------------------------------- | |
// add headers | |
var headerRow = svg | |
.append('g') | |
.attr('class', 'gridHeader') | |
// a group for every column | |
.selectAll('g') | |
.data(metaData.columnInfo) | |
.enter(); | |
var headerGroups = headerRow | |
.append('g') | |
.attr('width', params.cell.width) | |
.attr('height', params.grid.height) | |
.attr('class', function(d) { | |
return 'cell col-' + d.field; | |
}) | |
.attr('transform', function(d) { | |
var xvalue = metaData.colKeys.indexOf(d.field); | |
var x = xvalue * (params.cell.width + params.cell.paddingX); | |
return 'translate(' + x + ' 0)'; | |
}); | |
var headerText = headerGroups | |
.append('text') | |
// TODO: do this more cleanly and for better presentation | |
.attr('class', function(d) { | |
return 'headerText headerCol-' + d.field; | |
}) | |
.attr('data', function(d) { return d.field; }) | |
.attr('dy', params.cell.height/2 - 2) | |
.attr('dx', '2em') | |
.text(function(d) { | |
return d.title; | |
}); | |
// ---------------------------------------------------- | |
// Render the footers and ticks | |
// ---------------------------------------------------- | |
// add footers | |
var footerRow = svg | |
.append('g') | |
.attr('class', 'gridFooter') | |
.attr('transform', function(d) {; | |
return 'translate(0 ' + (params.header.height + params.grid.height) + ')'; | |
}) | |
// a group for every column | |
.selectAll('g') | |
.data(metaData.columnInfo) | |
.enter(); | |
var footerGroups = footerRow | |
.append('g') | |
.attr('width', params.cell.width) | |
.attr('height', params.grid.height) | |
.attr('class', function(d) { | |
return 'cell col-' + d.field; | |
}) | |
.attr('transform', function(d) { | |
var xvalue = metaData.colKeys.indexOf(d.field); | |
var x = xvalue * (params.cell.width + params.cell.paddingX); | |
return 'translate(' + x + ' 0)'; | |
}); | |
// create containers for ticks | |
var axis = footerGroups | |
.append('g') | |
.attr('class', function(d) { | |
return 'axis axis-' + d.field; | |
}) | |
.attr('transform', function(d) { | |
return 'translate(0 ' + (params.footer.marginY + params.footer.paddingY) + ')'; | |
}); | |
// define how ticks are created | |
var createTicks = function (headerGroups) { | |
var columnInfo = headerGroups.data(); | |
$.each(columnInfo, function(i, val) { | |
d3.select('.axis-' + val.field) | |
.call(d3.svg.axis() | |
.scale(scaleByProperty[val.field]) | |
.ticks(4) | |
// TODO: add in function for properly displaying the scale units (e.g. 1M instead of 1000000) | |
) | |
}); | |
}; | |
// create the ticks per column | |
footerGroups.call(createTicks); | |
// ---------------------------------------------------- | |
// Render the vertical lines | |
// ---------------------------------------------------- | |
// draw vertical zeroLine | |
$.each(metaData.columnInfo, function(i, val) { | |
d3.select('.gridHeader .col-' + val.field).append("line") | |
.attr('class', 'zeroLine') | |
.attr("x1", function (d) { | |
return scaleByProperty[val.field](0); | |
}) | |
.attr("x2", function (d) { | |
return scaleByProperty[val.field](0); | |
}) | |
.attr("y1", params.header.height) | |
.attr("y2", params.header.height) | |
.attr("stroke", params.zeroLine.strokeColor) | |
.attr("stroke-width", params.zeroLine.strokeWidth) | |
.attr("fill", "none") | |
.transition() | |
.delay(params.zeroLine.delay) | |
.duration(params.zeroLine.duration) | |
.attr("y2", params.header.height + params.grid.height); | |
}); | |
// draw left vertical left margin lines | |
$.each(metaData.columnInfo, function(i, val) { | |
d3.select('.gridHeader .col-' + val.field).append("line") | |
.attr('class', 'zeroLine') | |
.attr("x1", function (d) { | |
return scaleByProperty[val.field](minMaxByProperty[val.field].min); | |
}) | |
.attr("x2", function (d) { | |
return scaleByProperty[val.field](minMaxByProperty[val.field].min); | |
}) | |
.attr("y1", params.header.height) | |
.attr("y2", params.header.height) | |
.attr("stroke", params.marginLine.strokeColor) | |
.attr("stroke-width", params.marginLine.strokeWidth) | |
.attr("fill", "none") | |
.style("stroke-dasharray", ("3, 3")) | |
.transition() | |
.delay(params.marginLine.delay) | |
.duration(params.marginLine.duration) | |
.attr("y2", params.header.height + params.grid.height); | |
}); | |
// draw left vertical right margin lines | |
$.each(metaData.columnInfo, function(i, val) { | |
d3.select('.gridHeader .col-' + val.field).append("line") | |
.attr('class', 'zeroLine') | |
.attr("x1", function (d) { | |
return scaleByProperty[val.field](minMaxByProperty[val.field].max); | |
}) | |
.attr("x2", function (d) { | |
return scaleByProperty[val.field](minMaxByProperty[val.field].max); | |
}) | |
.attr("y1", params.header.height) | |
.attr("y2", params.header.height) | |
.attr("stroke", params.marginLine.strokeColor) | |
.attr("stroke-width", params.marginLine.strokeWidth) | |
.attr("fill", "none") | |
.style("stroke-dasharray", ("3, 3")) | |
.transition() | |
.delay(params.marginLine.delay) | |
.duration(params.marginLine.duration) | |
.attr("y2", params.header.height + params.grid.height); | |
}); | |
// shift entire grid | |
d3.select('.grid') | |
.attr('transform', 'translate(0 ' + params.header.height + ')'); | |
var updateRowOrder = function (rowsInNewOrder) { | |
// update row keys order | |
metaData.rowKeys = rowsInNewOrder.data().map(function(val) { | |
return val.key; | |
}) | |
d3.selectAll('.zeroLine, .marginLine') | |
.transition() | |
.duration(params.sort.duration * .1) | |
.attr('y2', params.header.height + params.chart.height * .5 ) | |
.transition() | |
.delay(params.sort.duration * .1) | |
.duration(params.sort.duration * .9) | |
.attr('y2', params.header.height + params.grid.height); | |
// translate each row to be in the right position | |
rowsInNewOrder | |
.transition() | |
.duration(function(d) { | |
if (params.sort.animate) { | |
var fractionOfDuration = params.sort.duration / metaData.rowKeys.length; | |
return fractionOfDuration * metaData.rowKeys.indexOf(d.key); | |
} else { | |
return 0; | |
} | |
}) | |
// TODO: add in appropriate dalays by row index to better vizualize it | |
.attr('transform', function(d) { | |
var yvalue = metaData.rowKeys.indexOf(d.key); | |
var y = yvalue * (params.cell.height + params.cell.paddingY); | |
return 'translate(0, ' + y + ')'; | |
}); | |
}; | |
// ---------------------------------------------------- | |
// register UI handlers | |
// ---------------------------------------------------- | |
// sort on click | |
if (params.sort.sortType > 0) { | |
d3.selectAll(".headerText").data(metaData.colKeys).on("click", function(key) { | |
// switch between sort order options (currently asc/dsc: [1, -1]) | |
metaData.incrementSortOrder(params.sort.sortType); | |
// get a new order for the rows (not yet applied) | |
d3.selectAll('g.row').sort(function(a,b) { | |
return (metaData.getSortOrder() * b[key]) - (metaData.getSortOrder() * a[key]); | |
}); | |
// apply the new sorted order | |
updateRowOrder(d3.selectAll('g.row')); | |
}); | |
} | |
// highlight row on hover | |
d3.selectAll(".row").on("mouseover", function(d) { | |
d3.select('.row-' + d.key).selectAll('rect.dataBar') | |
.transition() | |
.duration(200) | |
.style('fill', function(d) { | |
return d.value < 0 ? params.dataBar.hover.neg : params.dataBar.hover.pos; | |
}); | |
}); | |
// return after row on hover | |
d3.selectAll(".row").on("mouseout", function(d) { | |
d3.select('.row-' + d.key).selectAll('rect.dataBar') | |
.transition() | |
.duration(200) | |
.style('fill', function(d) { | |
return d.value < 0 ? params.dataBar.color.neg : params.dataBar.color.pos; | |
}); | |
}); | |
// send row info on row click | |
d3.selectAll(".row").on("click", function(d) { | |
// objects | |
var rowData = d, | |
rowHTML = $('.row-' + d.key); | |
// trigger event | |
$(el).trigger('rowClicked', {rowHTML: rowHTML, rowData: rowData}); | |
}); | |
return el; | |
}; | |
// ==================================================== | |
// build chart (exposed to public) | |
// ==================================================== | |
var buildChart = function (options) { | |
// hook up options to local vars and set defaults | |
var chartElement = options.el, | |
data = options.datasource, | |
columnInfo = options.columns, | |
chartParams = options.chartParams, // optional | |
metaDataParams = options.metaDataParams; // optional | |
// merge params into defaultParams (defaults get overridden if conflicts arise)) | |
chartParams = mergeStaticParams({params: chartParams}); | |
// clean data and return {data: data, metaData: metaData} | |
// datasource = {data: data, metaData: metaData} | |
// data = the actual data | |
var datasource = getData({data: data, columnInfo: columnInfo, metaDataParams: metaDataParams, chartParams: chartParams}); | |
// add in params that required calculations from data/metaData | |
chartParams = mergeDynamicParams({params: chartParams, el: chartElement, data: datasource.data, metaData: datasource.metaData}); | |
// draw the chart | |
// params: (data, metaData, element) | |
var chart = drawChart({ | |
data: datasource.data, | |
metaData: datasource.metaData, | |
el: $(chartElement)[0], | |
params: chartParams | |
}); | |
return chart; | |
}; | |
// if this is used as a module to extend a current module, namespace the returned object {d3chart: returnedObject} | |
return { | |
// expose private methods to be public | |
buildChart: buildChart | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"a": "-1", | |
"b": "5", | |
"c": "1.35", | |
"d": "500" | |
}, | |
{ | |
"a": "2", | |
"b": "100", | |
"c": "5", | |
"d": "77" | |
}, | |
{ | |
"a": "3", | |
"b": "-30", | |
"c": "-13.5", | |
"d": "1000" | |
}, | |
{ | |
"a": "2", | |
"b": "58", | |
"c": "31", | |
"d": "181" | |
}, | |
{ | |
"a": "7", | |
"b": "12", | |
"c": "1.2", | |
"d": "564" | |
}, | |
{ | |
"a": "3", | |
"b": "77", | |
"c": ".37", | |
"d": "274" | |
}, | |
{ | |
"a": "3", | |
"b": "-10", | |
"c": "-1.35", | |
"d": "365" | |
}, | |
{ | |
"a": "2", | |
"b": "83", | |
"c": "13", | |
"d": "210" | |
}, | |
{ | |
"a": "4", | |
"b": "23", | |
"c": "0.3", | |
"d": "640" | |
}, | |
{ | |
"a": "8", | |
"b": "51", | |
"c": ".25", | |
"d": "740" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style type="text/css"> | |
/* move CSS to elsewhere */ | |
/*.dataBar.neg { | |
fill: orange | |
} | |
.dataBar.pos { | |
fill: steelblue | |
}*/ | |
</style> | |
<div class="js-chart" style="width:100%; height:400px"></div> | |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> | |
<script src="http://d3js.org/d3.v2.min.js"></script> | |
<script src="../d3chart.js"></script> | |
<script> | |
// build chart | |
var sendDataToD3Chart = function (data) { | |
var d3chart = new D3Chart(); | |
var chart = d3chart.buildChart({ | |
el: '.js-chart', | |
datasource: { // "datasource: data " is also valid | |
data: data | |
}, | |
columns: | |
// example json for following columns | |
// [ | |
// { | |
// "a": "-1", | |
// "b": "5", | |
// "c": "1.35", | |
// "d": "1187" | |
// }, | |
// ... | |
// ] | |
[ | |
{ | |
title: "Alpha", // jQuery encodes this, so HTML characters are displayed literally | |
field: "a" // field name (no spaces, no special characters) | |
}, | |
{ | |
title: "Beta", | |
field: "b" | |
}, | |
{ | |
title: "Gamma", | |
field: "c" | |
}, | |
{ | |
title: "Delta", | |
field: "d" | |
} | |
], | |
chartParams: {}, // optional | |
metaDataParams: {} // optional | |
}); | |
// listen to events on element | |
var rowClicked = function (event, data) { | |
console.log(data); | |
}; | |
$(chart).on('rowClicked', rowClicked); | |
}; | |
// fetch data | |
// common implementations will be more robust | |
var data = $.getJSON('dummy-data.json') | |
.done(sendDataToD3Chart); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment