| 1 | {* |
| 2 | +--------------------------------------------------------------------+ |
| 3 | | Copyright CiviCRM LLC. All rights reserved. | |
| 4 | | | |
| 5 | | This work is published under the GNU AGPLv3 license with some | |
| 6 | | permitted exceptions and without any warranty. For full license | |
| 7 | | and copyright information, see https://civicrm.org/licensing | |
| 8 | +--------------------------------------------------------------------+ |
| 9 | *} |
| 10 | <script src="{$config->resourceBase}/bower_components/d3/d3.min.js"></script> |
| 11 | <script src="{$config->resourceBase}/bower_components/crossfilter2/crossfilter.min.js"></script> |
| 12 | <script src="{$config->resourceBase}/bower_components/dc-2.1.x/dc.min.js"></script> |
| 13 | <style src="{$config->resourceBase}/bower_components/dc-2.1.x/dc.min.css"></style> |
| 14 | {literal} |
| 15 | <style> |
| 16 | .dc-chart path.domain { |
| 17 | fill: none; |
| 18 | stroke: black; |
| 19 | } |
| 20 | </style> |
| 21 | <script type="text/javascript"> |
| 22 | function createChart( chartID, divName, xSize, ySize, data ) { |
| 23 | |
| 24 | var div = document.getElementById(divName); |
| 25 | if (!div) { |
| 26 | console.log("no element found for chart id ", divName); |
| 27 | return; |
| 28 | } |
| 29 | |
| 30 | // Figure out suitable size based on container size. |
| 31 | // In some cases the containing element has no size. We should insist on a minimum size. |
| 32 | var w = Math.max(Math.min(div.clientWidth - 32, 800), 316); |
| 33 | var h = Math.min(400, parseInt(w / 2)); |
| 34 | |
| 35 | var chartNode = document.createElement('div'); |
| 36 | var heading = document.createElement('h2'); |
| 37 | heading.textContent = data.title; |
| 38 | heading.style.marginBottom = '1rem'; |
| 39 | heading.style.textAlign = 'center'; |
| 40 | div.style.width = w + 'px'; |
| 41 | div.style.marginLeft = 'auto'; |
| 42 | div.style.marginRight = 'auto'; |
| 43 | |
| 44 | var links = document.createElement('div'); |
| 45 | links.style.textAlign = 'center'; |
| 46 | links.style.marginBottom = '1rem'; |
| 47 | var linkSVG = document.createElement('a'); |
| 48 | linkSVG.href = '#'; |
| 49 | linkSVG.textContent = 'Download chart (SVG)'; |
| 50 | linkSVG.addEventListener('click', e => { |
| 51 | e.preventDefault(); |
| 52 | e.stopPropagation(); |
| 53 | // Create an image. |
| 54 | var svg = div.querySelector('svg'); |
| 55 | var xml = new XMLSerializer().serializeToString(svg); |
| 56 | var image64 = 'data:image/svg+xml;base64,' + btoa(xml); |
| 57 | |
| 58 | downloadImageUrl('image/svg+xml', image64, data.title.replace(/[^a-zA-Z0-9-]+/g, '') + '.svg'); |
| 59 | }); |
| 60 | function downloadImageUrl(mime, url, filename) { |
| 61 | var downloadLink = document.createElement('a'); |
| 62 | downloadLink.download = filename; |
| 63 | downloadLink.href = url; |
| 64 | downloadLink.downloadurl = [mime, downloadLink.download, url].join(':'); |
| 65 | document.body.append(downloadLink); |
| 66 | downloadLink.click(); |
| 67 | document.body.removeChild(downloadLink); |
| 68 | } |
| 69 | var linkPNG = document.createElement('a'); |
| 70 | linkPNG.href = '#'; |
| 71 | linkPNG.textContent = 'Download chart (PNG)'; |
| 72 | linkPNG.addEventListener('click', e => { |
| 73 | e.preventDefault(); |
| 74 | e.stopPropagation(); |
| 75 | // Create an image. |
| 76 | |
| 77 | var canvas = document.createElement('canvas'); |
| 78 | canvas.width = w; |
| 79 | canvas.height = h; |
| 80 | div.appendChild(canvas); |
| 81 | |
| 82 | var svg = div.querySelector('svg'); |
| 83 | var xml = new XMLSerializer().serializeToString(svg); |
| 84 | var svg64 = btoa(xml); |
| 85 | var b64Start = 'data:image/svg+xml;base64,'; |
| 86 | var image64 = b64Start + svg64; |
| 87 | |
| 88 | var img = document.createElement('img'); |
| 89 | img.onload = function() { |
| 90 | canvas.getContext('2d').drawImage(img, 0, 0); |
| 91 | // canvas.style.display = 'block'; |
| 92 | var imgURL = canvas.toDataURL('image/png'); |
| 93 | downloadImageUrl('image/png', imgURL, data.title.replace(/[^a-zA-Z0-9-]+/g, '') + '.png'); |
| 94 | div.removeChild(canvas); |
| 95 | }; |
| 96 | img.src = image64; |
| 97 | }); |
| 98 | |
| 99 | links.appendChild(linkSVG); |
| 100 | links.appendChild(document.createTextNode(' | ')); |
| 101 | links.appendChild(linkPNG); |
| 102 | |
| 103 | var crossfilterData, ndx, dataDimension, dataGroup, chart; |
| 104 | ndx = crossfilter(data.values[0]); |
| 105 | dataDimension = ndx.dimension(d => d.label); |
| 106 | dataGroup = dataDimension.group().reduceSum(d => d.value); |
| 107 | var ordinals = data.values[0].map(d => d.label); |
| 108 | |
| 109 | if (data.type === 'barchart') { |
| 110 | chart = dc.barChart(chartNode) |
| 111 | .width(w) |
| 112 | .height(h) |
| 113 | .dimension(dataDimension) |
| 114 | .group(dataGroup) |
| 115 | .gap(4) // px |
| 116 | .x(d3.scale.ordinal(ordinals).domain(ordinals)) |
| 117 | .xUnits(dc.units.ordinal) |
| 118 | .margins({top: 10, right: 30, bottom: 30, left: 90}) |
| 119 | .elasticY(true) |
| 120 | .renderLabel(false) |
| 121 | .renderHorizontalGridLines(true) |
| 122 | .title(item=> item.key + ': ' + item.value) |
| 123 | //.turnOnControls(true) |
| 124 | .renderTitle(true); |
| 125 | } |
| 126 | else if (data.type === 'piechart') { |
| 127 | chart = dc.pieChart(chartNode) |
| 128 | .width(w) |
| 129 | .height(h) |
| 130 | .radius(parseInt(h / 2) - 5) // define pie radius |
| 131 | .innerRadius(parseInt(h / 4) - 5) // optional |
| 132 | .externalRadiusPadding(5) |
| 133 | .legend(dc.legend().legendText(d => d.name).y(5)) |
| 134 | .dimension(dataDimension) |
| 135 | .group(dataGroup) |
| 136 | .renderLabel(false) |
| 137 | .title(item=> item.key + ': ' + item.value) |
| 138 | .turnOnControls(true) |
| 139 | .renderTitle(true); |
| 140 | } |
| 141 | // Delay rendering so that animation looks good. |
| 142 | window.setTimeout(() => { |
| 143 | div.appendChild(heading); |
| 144 | div.appendChild(chartNode); |
| 145 | div.appendChild(links); |
| 146 | |
| 147 | dc.renderAll(); |
| 148 | }, 1500); |
| 149 | } |
| 150 | </script> |
| 151 | {/literal} |