constructor(el, props = {}) { console.log(props) //we initiate charts in constructor this.gainOrLossChart = dc.pieChart('#gain-loss-chart'); this.fluctuationChart = dc.barChart('#fluctuation-chart'); this.quarterChart = dc.pieChart('#quarter-chart'); this.dayOfWeekChart = dc.rowChart('#day-of-week-chart'); this.moveChart = dc.lineChart('#monthly-move-chart'); this.volumeChart = dc.barChart('#monthly-volume-chart'); this.yearlyBubbleChart = dc.bubbleChart('#yearly-bubble-chart'); this.nasdaqCount = dc.dataCount('.dc-data-count'); this.nasdaqTable = dc.dataTable('.dc-data-table'); }
d3.json('src/stores/untappd.json', function (error, data) { var beerData = data.response.beers.items; var fullDateFormat = d3.time.format('%a, %d %b %Y %X %Z'); var yearFormat = d3.time.format('%Y'); var monthFormat = d3.time.format('%b'); var dayOfWeekFormat = d3.time.format('%a'); // normalize/parse data so dc can correctly sort & bin them // I like to think of each "d" as a row in a spreadsheet beerData.forEach(d => { d.count = +d.count; // round to nearest 0.25 d.rating_score = Math.round(+d.rating_score * 4) / 4; d.beer.rating_score = Math.round(+d.beer.rating_score *4) / 4; // round to nearest 0.5 d.beer.beer_abv = Math.round(+d.beer.beer_abv * 2) / 2; // round to nearest 10 d.beer.beer_ibu = Math.floor(+d.beer.beer_ibu / 10) * 10; d.first_had_dt = fullDateFormat.parse(d.first_had); d.first_had_year = +yearFormat(d.first_had_dt); d.first_had_month = monthFormat(d.first_had_dt); d.first_had_day = dayOfWeekFormat(d.first_had_dt); }); // set crossfilter var ndx = crossfilter(beerData); // create dimensions (x-axis values) var yearDim = ndx.dimension(function(d) {return d.first_had_year;}), // pluck: short-hand for same kind of anon. function we used for yearDim monthDim = ndx.dimension(pluck('first_had_month')), dayOfWeekDim = ndx.dimension(pluck('first_had_day')), ratingDim = ndx.dimension(pluck('rating_score')), commRatingDim = ndx.dimension(function(d) {return d.beer.rating_score;}), abvDim = ndx.dimension(function(d) {return d.beer.beer_abv;}), ibuDim = ndx.dimension(function(d) {return d.beer.beer_ibu;}), allDim = ndx.dimension(function(d) {return d;}); // create groups (y-axis values) var all = ndx.groupAll(); var countPerYear = yearDim.group().reduceCount(), countPerMonth = monthDim.group().reduceCount(), countPerDay = dayOfWeekDim.group().reduceCount(), countPerRating = ratingDim.group().reduceCount(), countPerCommRating = commRatingDim.group().reduceCount(), countPerABV = abvDim.group().reduceCount(), countPerIBU = ibuDim.group().reduceCount(); // specify charts var yearChart = pieChart('#chart-ring-year'), monthChart = pieChart('#chart-ring-month'), dayChart = pieChart('#chart-ring-day'), ratingCountChart = barChart('#chart-rating-count'), commRatingCountChart = barChart('#chart-community-rating-count'), abvCountChart = barChart('#chart-abv-count'), ibuCountChart = barChart('#chart-ibu-count'); var myDataCount = dataCount('#data-count'); var myDataTable = dataTable('#data-table'); yearChart .width(150) .height(150) .dimension(yearDim) .group(countPerYear) .innerRadius(20); monthChart .width(150) .height(150) .dimension(monthDim) .group(countPerMonth) .innerRadius(20) .ordering(function (d) { var order = { 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 }; return order[d.key]; }); dayChart .width(150) .height(150) .dimension(dayOfWeekDim) .group(countPerDay) .innerRadius(20) .ordering(function (d) { var order = { 'Mon': 0, 'Tue': 1, 'Wed': 2, 'Thu': 3, 'Fri': 4, 'Sat': 5, 'Sun': 6 } return order[d.key]; } ); ratingCountChart .width(300) .height(180) .dimension(ratingDim) .group(countPerRating) .x(d3.scale.linear().domain([0,5.2])) .elasticY(true) .centerBar(true) .barPadding(5) .xAxisLabel('My rating') .yAxisLabel('Count') .margins({top: 10, right: 20, bottom: 50, left: 50}); ratingCountChart.xAxis().tickValues([0, 1, 2, 3, 4, 5]); commRatingCountChart .width(300) .height(180) .dimension(commRatingDim) .group(countPerCommRating) .x(d3.scale.linear().domain([0,5.2])) .elasticY(true) .centerBar(true) .barPadding(5) .xAxisLabel('Community rating') .yAxisLabel('Count') .margins({top: 10, right: 20, bottom: 50, left: 50}); commRatingCountChart.xAxis().tickValues([0, 1, 2, 3, 4, 5]); abvCountChart .width(300) .height(180) .dimension(abvDim) .group(countPerABV) .x(d3.scale.linear().domain([-0.2, d3.max(beerData, function (d) { return d.beer.beer_abv; }) + 0.2])) .elasticY(true) .centerBar(true) .barPadding(2) .xAxisLabel('Alcohol By Volume (%)') .yAxisLabel('Count') .margins({top: 10, right: 20, bottom: 50, left: 50}); ibuCountChart .width(300) .height(180) .dimension(ibuDim) .group(countPerIBU) .x(d3.scale.linear().domain([-2, d3.max(beerData, function (d) { return d.beer.beer_ibu; }) + 2])) .elasticY(true) .centerBar(true) .barPadding(5) .xAxisLabel('International Bitterness Units') .yAxisLabel('Count') .xUnits(function (d) { return 5;}) .margins({top: 10, right: 20, bottom: 50, left: 50}); myDataCount .dimension(ndx) .group(all); myDataTable .dimension(allDim) .group(function (d) { return 'dc.js insists on putting a row here so I remove it using JS'; }) .size(100) .columns([ function (d) { return d.brewery.brewery_name; }, function (d) { return d.beer.beer_name; }, function (d) { return d.beer.beer_style; }, function (d) { return d.rating_score; }, function (d) { return d.beer.rating_score; }, function (d) { return d.beer.beer_abv; }, function (d) { return d.beer.beer_ibu; } ]) .sortBy(pluck('rating_score')) .order(d3.descending) .on('renderlet', function (table) { // each time table is rendered remove nasty extra row dc.js insists on adding table.select('tr.dc-table-group').remove(); }); // register handlers d3.selectAll('a#all').on('click', function () { filterAll(); renderAll(); }); d3.selectAll('a#year').on('click', function () { yearChart.filterAll(); redrawAll(); }); d3.selectAll('a#month').on('click', function () { monthChart.filterAll(); redrawAll(); }); d3.selectAll('a#day').on('click', function () { dayChart.filterAll(); redrawAll(); }); // showtime! renderAll(); });
renderDC = (selectedTimespan) => { const { formatMessage } = this.props.intl; // show a spinner while it loads this.showLoading(true); // set up chart containers const storiesOverTimeChart = dc.barChart('#stories-over-time-chart'); const storyCount = dc.dataCount('#story-counts'); const languageChart = dc.pieChart('#language-chart'); const TopicStoryTable = dc.dataTable('#story-table'); const facebookShareChart = dc.barChart('#facebook-share-chart'); const inlinkChart = dc.barChart('#inlink-chart'); // load the data up d3.csv(this.csvFileUrl(), (data) => { this.showLoading(false); // set up some binning const maxFacebookShares = d3.max(data.map(d => d.facebook_share_count)); const facebookBinSize = maxFacebookShares / FACEBOOK_BIN_COUNT; const maxInlinks = d3.max(data.map(d => d.media_inlink_count)); const inlinkBinSize = d3.max([1, (maxInlinks / INLINK_BIN_COUNT)]); // don't do < 1 for bin size // clean up the data for (let i = 0; i < data.length; i += 1) { const d = data[i]; d.publishDate = (d.publish_date === STORY_PUB_DATE_UNDATEABLE) ? null : storyPubDateToMoment(d.publish_date).toDate(); d.publishMonth = (d.publish_date === STORY_PUB_DATE_UNDATEABLE) ? null : d.publishDate.getMonth(); // pre-calculate month for better performance d.facebook_share_count = +d.facebook_share_count; d.inlink_count = +d.inlink_count; d.media_id = +d.media_id; d.media_inlink_count = +d.media_inlink_count; d.outlink_count = +d.outlink_count; d.domain = storyDomainName(d); } // set up all the dimensions and groups that feed the charts const dateExtents = [selectedTimespan.startDateObj, selectedTimespan.endDateObj]; const totalDays = moment(selectedTimespan.endDateObj).diff(moment(selectedTimespan.startDateObj), 'days'); const ndx = crossfilter(data); const all = ndx.groupAll(); const publishDateDimension = ndx.dimension(d => d.publishDate); const publishDateGroup = publishDateDimension.group(); // .reduceSum(() => 1); const languageDimension = ndx.dimension(d => d.language); const languageGroup = languageDimension.group(); const facebookDimension = ndx.dimension(d => d.facebook_share_count); const facebookGroup = facebookDimension.group(d => Math.floor(d / facebookBinSize) * facebookBinSize); const inlinkDimension = ndx.dimension(d => d.media_inlink_count); const inlinkGroup = inlinkDimension.group(d => Math.floor(d / inlinkBinSize) * inlinkBinSize); // histogram of stories storiesOverTimeChart .width(1000).height(200) .dimension(publishDateDimension) .group(publishDateGroup) .elasticY(true) .gap(0) .x(d3.scaleTime().domain(dateExtents)) .xUnits(() => totalDays) .yAxisLabel(formatMessage(localMessages.storiesChartY)) .xAxisLabel(formatMessage(localMessages.storiesChartX)); // language pie chart languageChart .width(180).height(180) .radius(80) .turnOnControls(true) .controlsUseVisibility(false) .dimension(languageDimension) .group(languageGroup); // facebook share histogram facebookShareChart .width(380).height(200) .group(facebookGroup) .dimension(facebookDimension) .gap(1) .x(d3.scaleLinear().domain([0, maxFacebookShares])) .xUnits(() => FACEBOOK_BIN_COUNT) .xAxisLabel(formatMessage(localMessages.facebookChartX)) .yAxisLabel(formatMessage(localMessages.facebookChartY)) .renderHorizontalGridLines(true); // media inlink histogram inlinkChart .width(380).height(200) .group(inlinkGroup) .dimension(inlinkDimension) .gap(1) .x(d3.scaleLinear().domain([0, maxInlinks])) .xUnits(() => d3.min([maxInlinks, INLINK_BIN_COUNT])) .yAxisLabel(formatMessage(localMessages.inlinkChartY)) .xAxisLabel(formatMessage(localMessages.inlinkChartX)) .renderHorizontalGridLines(true); // show how many stories are included storyCount .dimension(ndx) .group(all); // and set up the story table TopicStoryTable .dimension(publishDateDimension) .size(50) .group((d) => { let monthStr = null; if (d.publishDate !== null) { // ignore undateable ones const format = d3.format('02d'); monthStr = `${d.publishDate.getFullYear()}/${format((d.publishDate.getMonth() + 1))}`; } return monthStr; }) .showGroups(false) .columns([ d => ((d.publishDate === null) ? STORY_PUB_DATE_UNDATEABLE : moment(d.publishDate).format('MMM D, YYYY')), d => `<img className="google-icon" src=${googleFavIconUrl(d.domain)} alt=${d.domain} />`, d => d.media_name, d => `<a href=${d.url} target="_blank">${d.title}</a>`, d => d.media_inlink_count, d => d.outlink_count, d => d.facebook_share_count, ]) .sortBy(d => d.media_inlink_count) // TODO: make this selectable .order(d3.descending); this.setState({ charts: { languageChart, facebookShareChart, inlinkChart, storiesOverTimeChart, }, }); dc.renderAll(); }); }
$(function () { var dateFormat = d3.time.format('%Y-%m-%d'); //todo custom reduce functions for server-side stuff var volumeChart = dc.barChart('#volume-chart'); var valueChart = dc.compositeChart('#value-chart'); var amountChart = dc.barChart(valueChart); var cumAmountChart = dc.lineChart(valueChart); var accountChart = dc.rowChart('#account-selector'); var typeChart = dc.rowChart('#account-type-selector'); var transactionTable = dc.dataTable('#transaction-table'); var transactionCount = dc.dataCount(".dc-data-count"); var params = (new URL(document.location)).searchParams; var dateMin = dateFormat.parse(params.get('after')); var dateMax = dateFormat.parse(params.get('before')); $("#reset-all").click(function () { dc.filterAll(); dc.renderAll(); return false; }); $("#reset-volume-chart").click(function () { volumeChart.filterAll(); dc.redrawAll(); return false; }); var url = new URL($SCRIPT_ROOT + '/transactions/json'); url.search = document.location.search; d3.json(url.toString(), function (resp) { data = resp.items; data.forEach(function (d) { d.dd = dateFormat.parse(d.valid_on); d.month = d3.time.month(d.dd); }); var ndx = crossfilter(data); var all = ndx.groupAll(); var transaction = ndx.dimension(function (d) { return d.account_id; }); var transactionGroup = transaction.groupAll(); transactionCount .dimension(ndx) .group(transactionGroup); var account = ndx.dimension(function (d) { return d.account_id; }); var accountGroup = account.group(); var accountCache = {"Others": "Other accounts"}; //dict of values cached var accountReq = new Set([]); //set of ids being requested // todo url_for var accountName = function (acc_id, format_func, action_func) { if (!(acc_id in accountCache)) { var href = "/finance/accounts/" + acc_id; if (!accountReq.has(acc_id)) { accountReq.add(acc_id); jQuery.getJSON(href + "/json?limit=0", function (data) { accountCache[acc_id] = data.name; action_func(acc_id, data.name); }).complete(function () { accountReq.delete(acc_id); }); } return format_func(acc_id); } else { return accountCache[acc_id]; } }; accountChart .height(300) .width(250) .dimension(account) .group(accountGroup) .cap(10) .x(d3.scale.linear().range([1, 100])) .label(function (d) { var format_func = function (acc_id) { return "acc-" + acc_id; }; var action_func = function (acc_id, replacement) { jQuery('text:contains("' + format_func(acc_id) + '")').text(replacement); }; return accountName(d.key, format_func, action_func); }) .ordering(function (d) { return -d.value; }) .renderLabel(true) .xAxis().tickValues([]); var accountType = ndx.dimension(function (d) { return d.type; }); var accountTypeGroup = accountType.group(); typeChart .height(300) .width(250) .dimension(accountType) .group(accountTypeGroup) .cap(10) .x(d3.scale.linear().range([1, 100])) .label(function (d) { return d.key + " (" + d.value + ")"; }) .renderLabel(true) .xAxis().tickValues([]); var dateDimension = ndx.dimension(function (d) { return d.dd; }); var monthDimension = ndx.dimension(function (d) { return d.month; }); var dateAccessor = function (d) { return d.dd; }; dateExtent = []; dateExtent = d3.extent(data, dateAccessor); if (!(dateMin === null)) { dateExtent[0] = dateMin; } if (!(dateMax === null)) { dateExtent[1] = dateMax; } var monthGroup = monthDimension.group().reduceCount(); volumeChart .width(700) .height(100) .dimension(monthDimension) .group(monthGroup) .x(d3.time.scale().domain(dateExtent)) .gap(0) // gap(0) has overlaps for some reason .xUnits(d3.time.months) //seems to be buggy, so we can't set bar width :( // logscale would also be nice, but that too is buggy .elasticY(true) .margins({top: 0, left: 70, right: 70, bottom: 25}) .renderHorizontalGridLines(true) .renderVerticalGridLines(true) .yAxisLabel("# transactions"); var valueGroup = monthDimension.group().reduceSum(function (d) { return d.amount; }); var cumValueGroup = { all: function () { var s = 0; var g = []; valueGroup.all().forEach(function (d, i) { s += d.value; g.push({key: d.key, value: s}); }); return g; }, }; amountChart .dimension(monthDimension) .group(valueGroup, "Monthly amount transacted"); cumAmountChart .dimension(monthDimension) .group(cumValueGroup, "Cumulative amount transacted") .ordinalColors(["orange"]) .useRightYAxis(true) .interpolate("step-after"); valueChart .width(700) .height(200) .dimension(monthDimension) .x(d3.time.scale().domain(dateExtent)) .xUnits(d3.time.months) .elasticY(true) .brushOn(false) .margins({top: 0, left: 70, right: 70, bottom: 25}) .renderHorizontalGridLines(true) .renderVerticalGridLines(true) .yAxisLabel("monthly net amount transacted") .rightYAxisLabel("total net amount transacted") .rangeChart(volumeChart) .legend(dc.legend().x(90).y(0).itemHeight(13).gap(5)) .compose([amountChart, cumAmountChart]); var linkTemplate = _.template( '<a id="<%= id %>" href="<%= href %>"><%= title %></a>', ); var descCache = {}; var descReq = new Set([]); transactionTable .dimension(dateDimension) .columns([ function (d) { return d.amount / 100. + " €"; }, function (d) { var format_func = function (acc_id) { return "<span id=\"acc-" + acc_id + "\"></span>"; }; var action_func = function (acc_id, replacement) { jQuery('#acc-' + acc_id).text(replacement); }; return accountName(d.account_id, format_func, action_func); }, function (d) { return d.type; }, ]) .group(function (d) { var href = "/finance/transactions/" + d.id; // if building template is too slow, jquery may be executed // before document is generated :( var desc = "Link"; if (!(d.id in descCache)) { if (!descReq.has(d.id)) { descReq.add(d.id); jQuery.getJSON(href + "/json", function (data) { jQuery('a[href="' + href + '"]').text(data.description); descCache[d.id] = data.description; descReq.delete(d.id); }); } } else { desc = descCache[d.id]; } var date = d3.time.format("%Y-%m-%d")(d.dd); var link = linkTemplate({ 'id': "transaction-link", 'href': href, 'title': desc, }); return date + " " + link; }) .sortBy(function (d) { return -d.id; }) .size(15); //TODO transaction value chart (count vs value) //TODO total value transacted chart (count vs value) dc.renderAll(); }); });