From f9f60cec1940d30d14276cf545fa57cf4fac8cb1 Mon Sep 17 00:00:00 2001 From: James Saunders Date: Fri, 29 Dec 2017 22:07:35 +0000 Subject: [PATCH] HeatRing Component v2.1.8 * New heatRing component. * Various Tidy ups. --- Makefile | 3 +- build/d3-ez.css | 22 +- build/d3-ez.js | 760 ++++++++++--------------- build/d3-ez.min.js | 2 +- build/d3-ez.zip | Bin 34819 -> 33495 bytes css/barGrouped.css | 2 +- css/circularHeat.css | 8 +- css/donut.css | 2 +- css/punchCard.css | 2 +- css/radialBar.css | 2 +- css/tabularHeat.css | 6 +- examples/CircularHeatChartExample.html | 3 + examples/RadialBarChartExample.html | 3 + examples/Showcase.html | 3 +- examples/Vega.html | 96 ---- package.json | 2 +- src/chart/circularHeat.js | 102 +++- src/chart/discreteBar.js | 59 +- src/chart/donut.js | 38 +- src/chart/groupedBar.js | 79 +-- src/chart/multiSeriesLine.js | 62 +- src/chart/punchCard.js | 97 ++-- src/chart/radialBar.js | 70 ++- src/chart/tabularHeat.js | 69 +-- src/component/barStacked.js | 2 +- src/component/circularLabels.js | 2 +- src/component/donut.js | 8 +- src/component/heatCircle.js | 165 ------ src/component/heatMap.js | 24 +- src/component/heatRing.js | 130 +++++ src/header.js | 2 +- src/vega.js | 210 ------- test/DataTest.html | 4 +- test/d3-ez.js | 3 +- 34 files changed, 809 insertions(+), 1233 deletions(-) delete mode 100644 examples/Vega.html delete mode 100644 src/component/heatCircle.js create mode 100644 src/component/heatRing.js delete mode 100644 src/vega.js diff --git a/Makefile b/Makefile index c978f8fa..bc9d297f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ JS_FILES := src/header.js \ src/baseFunctions.js \ src/chart.js \ - src/vega.js \ src/colors.js \ src/component/barGrouped.js \ src/component/barStacked.js \ @@ -15,7 +14,7 @@ JS_FILES := src/header.js \ src/component/legend.js \ src/component/lineChart.js \ src/component/heatMap.js \ - src/component/heatCircle.js \ + src/component/heatRing.js \ src/component/punchCard.js \ src/component/numberCard.js \ src/component/title.js \ diff --git a/build/d3-ez.css b/build/d3-ez.css index 12eff5ab..fc9feb0e 100644 --- a/build/d3-ez.css +++ b/build/d3-ez.css @@ -52,7 +52,7 @@ .barGrouped rect.bar, .barStacked rect.bar { stroke: #999999; - stroke-width: 1px; + stroke-width: 0.5px; } .barGrouped rect.bar:hover, .barStacked rect.bar:hover { @@ -70,7 +70,7 @@ .chartRadialBar path.segment { fill: steelblue; - stroke-width: 1px; + stroke-width: 0.5px; stroke: #999999; } @@ -105,10 +105,16 @@ /* === Circular Heat Chart Styles === */ .chartCircularHeat path.segment { - stroke: #B6B6B6; + stroke: #999999; stroke-width: 0.5px; } +.chartCircularHeat path.segment:hover { + opacity: 0.7; + stroke: black; + cursor: pointer; +} + .chartCircularHeat .segmentLabels text { font-size: 1.0em; font-weight: bold; @@ -123,12 +129,12 @@ /* === Tabular Heat Chart Styles === */ -.chartTabularHeat rect.card { +.chartTabularHeat rect.cell { stroke: #999999; - stroke-width: 1px; + stroke-width: 0.5px; } -.chartTabularHeat rect.card:hover { +.chartTabularHeat rect.cell:hover { opacity: 0.7; stroke: black; cursor: pointer; @@ -143,7 +149,7 @@ .chartDonut path.slice { stroke: #999999; - stroke-width: 1px; + stroke-width: 0.5px; } .chartDonut path.slice:hover { @@ -164,7 +170,7 @@ .chartPunchCard circle.punchSpot { stroke: #999999; - stroke-width: 1px; + stroke-width: 0.5px; } .chartPunchCard circle.punchSpot:hover { diff --git a/build/d3-ez.js b/build/d3-ez.js index 2e6e8441..568a42ff 100644 --- a/build/d3-ez.js +++ b/build/d3-ez.js @@ -6,7 +6,7 @@ * @license GPLv3 */ d3.ez = { - version: "2.1.7", + version: "2.1.8", author: "James Saunders", copyright: "Copyright (C) 2017 James Saunders", license: "GPL-3.0" @@ -342,167 +342,6 @@ d3.ez.chart = function module() { return my; }; -/** - * Vega - * - * @example - * @todo - */ -d3.ez.vega = function module() { - // SVG and Canvas containers (Populated by 'my' function) - var svg; - var canvas; - // Default Options (Configurable via setters) - var width = 1e3; - var height = 600; - var margin = { - top: 30, - right: 30, - bottom: 30, - left: 30 - }; - var padding = 20; - var canvasW = 580; - var canvasH = 380; - var chartTop = 0; - var classed = "d3ez"; - var chart = undefined; - var yScale = undefined; - var yAxis = undefined; - var xScale = undefined; - var xAxis = undefined; - var legend = undefined; - var title = undefined; - var creditTag = d3.ez.component.creditTag(); - var yAxisLabel = ""; - // Colours - var colorScale = undefined; - // Dispatch (custom events) - var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); - function init(data) { - canvasW = width - (margin.left + margin.right); - canvasH = height - (margin.top + margin.bottom); - // Init Chart - chart.colorScale(colorScale).xScale(xScale).yScale(yScale).width(canvasW).height(canvasH).dispatch(dispatch); - // Init Legend - if (legend) { - legend.width(120).height(200); - chart.width(chart.width() - legend.width() - padding); - } - // Init Title - if (title) { - chartTop = title.height() + padding; - chart.height(chart.height() - title.height() - padding); - } - // Have we changed the graph size? - xScale.range([ chart.width(), 0 ]); - yScale.range([ chart.height(), 0 ]); - // Init Axis - xAxis = d3.axisBottom(xScale); - yAxis = d3.axisLeft(yScale); - // Init Credit Tag - creditTag.text("d3-ez.net").href("http://d3-ez.net"); - } - function my(selection) { - selection.each(function(data) { - init(data); - // Create SVG element (if it does not exist already) - if (!svg) { - svg = d3.select(this).append("svg").classed(classed, true).attr("width", width).attr("height", height); - canvas = svg.append("g").classed("canvas", true); - canvas.append("g").classed("chartbox", true); - canvas.select(".chartbox").append("g").classed("x-axis axis", true); - canvas.select(".chartbox").append("g").classed("y-axis axis", true); - canvas.append("g").classed("legendbox", true); - canvas.append("g").classed("titlebox", true); - svg.append("g").classed("creditbox", true); - } else { - canvas = svg.select(".canvas"); - } - // Update the canvas dimensions - canvas.attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", canvasW).attr("height", canvasH); - // Add Chart - canvas.select(".chartbox").attr("transform", "translate(0," + chartTop + ")").datum(data).call(chart); - canvas.select(".y-axis").call(yAxis); - canvas.select(".x-axis").attr("transform", "translate(0," + chart.height() + ")").call(xAxis); - // Add Title - if (title) { - canvas.select(".titlebox").attr("transform", "translate(" + width / 2 + "," + 0 + ")").call(title); - } - // Add Legend - if (legend && (typeof chart.colorScale === "function" || typeof chart.sizeScale === "function")) { - if (typeof chart.colorScale === "function") { - legend.colorScale(chart.colorScale()); - } - if (typeof chart.sizeScale === "function") { - legend.sizeScale(chart.sizeScale()); - } - canvas.select(".legendbox").attr("transform", "translate(" + (canvasW - legend.width()) + "," + chartTop + ")").call(legend); - } - // Add Credit Tag - if (creditTag) { - svg.select(".creditbox").attr("transform", "translate(" + (width - 10) + "," + (height - 5) + ")").call(creditTag); - } - }); - } - // Configuration Getters & Setters - my.width = function(_) { - if (!arguments.length) return width; - width = _; - return this; - }; - my.height = function(_) { - if (!arguments.length) return height; - height = _; - return this; - }; - my.chart = function(_) { - if (!arguments.length) return chart; - chart = _; - return this; - }; - my.colorScale = function(_) { - if (!arguments.length) return colorScale; - colorScale = _; - return this; - }; - my.xScale = function(_) { - if (!arguments.length) return xScale; - xScale = _; - return this; - }; - my.yScale = function(_) { - if (!arguments.length) return yScale; - yScale = _; - return this; - }; - my.legend = function(_) { - if (!arguments.length) return legend; - legend = _; - return this; - }; - my.title = function(_) { - if (!arguments.length) return title; - if (typeof _ === "string") { - // If the caller has passed a plain string convert it to a title object. - title = d3.ez.title().mainText(_).subText(""); - } else { - title = _; - } - return this; - }; - my.yAxisLabel = function(_) { - if (!arguments.length) return yAxisLabel; - yAxisLabel = _; - return this; - }; - my.on = function() { - var value = dispatch.on.apply(dispatch, arguments); - return value === dispatch ? my : value; - }; - return my; -}; - /** * Colour Palettes * @@ -909,10 +748,12 @@ d3.ez.component.donut = function module() { var defaultRadius = Math.min(width, height) / 2; radius = typeof radius === "undefined" ? defaultRadius : radius; innerRadius = typeof innerRadius === "undefined" ? defaultRadius / 2 : innerRadius; + // Pie Generator var pie = d3.pie().value(function(d) { return d.value; - }).sort(null); - var arc = d3.arc().innerRadius(innerRadius).outerRadius(radius).cornerRadius(3).padAngle(.015); + }).sort(null).padAngle(.015); + // Arc Generators + var arc = d3.arc().innerRadius(innerRadius).outerRadius(radius).cornerRadius(2); var outerArc = d3.arc().innerRadius(radius * .9).outerRadius(radius * .9); function arcTween(d) { var i = d3.interpolate(this._current, d); @@ -1461,8 +1302,8 @@ d3.ez.component.lineChart = function module() { */ d3.ez.component.heatMap = function module() { // Default Options (Configurable via setters) - var height = 100; var width = 300; + var height = 100; var colorScale = undefined; var xScale = undefined; var yScale = undefined; @@ -1475,24 +1316,24 @@ d3.ez.component.heatMap = function module() { selection.each(function(data) { var cellHeight = yScale.bandwidth(); var cellWidth = xScale.bandwidth(); - selection.selectAll(".deck").data(function(d) { + var heatMap = selection.selectAll(".heatMap").data(function(d) { return [ d ]; - }).enter().append("g").classed("deck", true).on("click", function(d) { + }).enter().append("g").classed("heatMap", true).on("click", function(d) { dispatch.call("customClick", this, d); }); - var deck = selection.selectAll(".deck"); - var cards = deck.selectAll(".card").data(function(d) { + heatMap = selection.selectAll(".heatMap").merge(heatMap); + var cells = heatMap.selectAll(".cell").data(function(d) { return d.values; }); - cards.enter().append("rect").attr("x", function(d, i) { + cells.enter().append("rect").attr("x", function(d, i) { return xScale(d.key); - }).attr("y", 0).attr("rx", 3).attr("ry", 3).attr("class", "card").attr("width", cellWidth).attr("height", cellHeight).on("click", dispatch.customClick).on("mouseover", function(d) { + }).attr("y", 0).attr("rx", 2).attr("ry", 2).attr("class", "cell").attr("width", cellWidth).attr("height", cellHeight).on("click", dispatch.customClick).on("mouseover", function(d) { dispatch.call("customMouseOver", this, d); - }).merge(cards).transition().duration(1e3).attr("fill", function(d) { + }).merge(cells).transition().duration(1e3).attr("fill", function(d) { return colorScale(d.value); }); - cards.exit().remove(); - cards.select("title").text(function(d) { + cells.exit().remove(); + cells.select("title").text(function(d) { return d.value; }); }); @@ -1536,18 +1377,20 @@ d3.ez.component.heatMap = function module() { }; /** - * Reusable Circular Heat Map + * Reusable Circular Heat Ring * * @example - * var myBars = d3.ez.component.heatCircle() + * var myBars = d3.ez.component.heatRing() * .colorScale(**D3 Scale Object**); * d3.select("svg").call(myBars); */ -d3.ez.component.heatCircle = function module() { +d3.ez.component.heatRing = function module() { // Default Options (Configurable via setters) var width = 400; var height = 300; var colorScale = undefined; + var xScale = undefined; + var yScale = undefined; var transition = { ease: d3.easeBounce, duration: 500 @@ -1557,68 +1400,37 @@ d3.ez.component.heatCircle = function module() { var innerRadius = undefined; function my(selection) { selection.each(function(data) { - // Slice Data, calculate totals, max etc. - var slicedData = d3.ez.dataParse(data); - groupNames = slicedData.groupNames; - numRadials = groupNames.length; - categoryNames = slicedData.categoryNames; - numSegments = categoryNames.length; var defaultRadius = Math.min(width, height) / 2; radius = typeof radius === "undefined" ? defaultRadius : radius; innerRadius = typeof innerRadius === "undefined" ? defaultRadius / 4 : innerRadius; - var segmentHeight = (radius - innerRadius) / numRadials; + // Pie Generator + var pie = d3.pie().value(function(d) { + return 1; + }).sort(null).padAngle(.015); // Arc Generator - var arc = d3.arc().innerRadius(function(d, i) { - return innerRadius + segmentHeight * d.ring; - }).outerRadius(function(d, i) { - return innerRadius + segmentHeight * (d.ring + 1); - }).startAngle(function(d, i) { - return i * 2 * Math.PI / numSegments; - }).endAngle(function(d, i) { - return (i + 1) * 2 * Math.PI / numSegments; - }); + var arc = d3.arc().outerRadius(radius).innerRadius(innerRadius).cornerRadius(2); // Create chart group - var circularHeat = selection.selectAll(".heatCircle").data(function(d) { + var heatRing = selection.selectAll(".heatRing").data(function(d) { return [ d ]; - }).enter().append("g").classed("heatCircle", true).on("click", function(d) { + }).enter().append("g").classed("heatRing", true).on("click", function(d) { dispatch.call("customClick", this, d); }); - circularHeat.append("g").attr("class", "rings"); - circularHeat.append("g").attr("class", "radialLabels"); - var circularHeat = selection.selectAll(".heatCircle").merge(circularHeat); - // Rings - circularHeat.select(".rings").selectAll("g").data(function(d) { - return d; - }).enter().append("g").classed("ring", true); + heatRing = selection.selectAll(".heatRing").merge(heatRing); + var segments = heatRing.selectAll(".segment").data(function(d) { + var key = d.key; + var data = pie(d.values); + data.forEach(function(d, i) { + data[i].key = key; + }); + return data; + }); // Ring Segments - circularHeat.selectAll(".ring").selectAll("path").data(function(d, i) { - // Add index (used to calculate ring number) - for (j = 0; j < numSegments; j++) { - d.values[j].ring = i; - } - return d.values; - }).enter().append("path").attr("d", arc).attr("fill", function(d) { - return colorScale(d.value); + segments.enter().append("path").attr("d", arc).attr("fill", function(d) { + return colorScale(d.data.value); }).classed("segment", true).on("mouseover", function(d) { - dispatch.call("customMouseOver", this, d); - }); - // Radial Labels - var lsa = .01; - // Label start angle - var radialLabels = circularHeat.select(".radialLabels").classed("labels", true); - radialLabels.selectAll("def").data(function(d) { - return d; - }).enter().append("def").append("path").attr("id", function(d, i) { - return "radial-label-path-" + i; - }).attr("d", function(d, i) { - var r = innerRadius + (i + .2) * segmentHeight; - return "m" + r * Math.sin(lsa) + " -" + r * Math.cos(lsa) + " a" + r + " " + r + " 0 1 1 -1 0"; - }); - radialLabels.selectAll("text").data(groupNames).enter().append("text").append("textPath").attr("xlink:href", function(d, i) { - return "#radial-label-path-" + i; - }).text(function(d) { - return d; + dispatch.call("customMouseOver", this, d.data); }); + segments.exit().remove(); }); } // Configuration Getters & Setters @@ -1647,6 +1459,16 @@ d3.ez.component.heatCircle = function module() { colorScale = _; return my; }; + my.xScale = function(_) { + if (!arguments.length) return xScale; + xScale = _; + return my; + }; + my.yScale = function(_) { + if (!arguments.length) return yScale; + yScale = _; + return my; + }; my.dispatch = function(_) { if (!arguments.length) return dispatch(); dispatch = _; @@ -2009,7 +1831,7 @@ d3.ez.component.circularLabels = function module() { selection.each(function(data) { var defaultRadius = Math.min(width, height) / 2; radius = typeof radius === "undefined" ? defaultRadius : radius; - var labelRadius = radius * 1.05; + var labelRadius = radius * 1.02; var circularLabels = selection.selectAll(".circularLabels").data(function(d) { return [ d ]; }).enter().append("g").classed("circularLabels", true); @@ -2216,21 +2038,13 @@ d3.ez.component.htmlList = function module() { /** * Discrete Bar Chart * - * @example - * var myChart = d3.ez.chart.discreteBar() - * .width(400) - * .height(300) - * .transition({ease: "bounce", duration: 1500}) - * .colors(d3.scaleCategory10().range()); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.discreteBar = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartDiscreteBar"; var width = 400; var height = 300; var margin = { @@ -2244,37 +2058,38 @@ d3.ez.chart.discreteBar = function module() { duration: 500 }; var colors = d3.ez.colors.categorical(4); - var gap = 0; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; - var yAxisLabel = undefined; + // Chart Dimensions + var chartW; + var chartH; + // Scales and Axis + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = undefined; function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var categoryNames = slicedData.categoryNames; - var maxValue = slicedData.maxValue; - var yAxisLabel = slicedData.groupName; + categoryNames = slicedData.categoryNames; + maxValue = slicedData.maxValue; + yAxisLabel = slicedData.groupName; + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand().domain(categoryNames).rangeRound([ 0, chartW ]).padding(.15); yScale = d3.scaleLinear().domain([ 0, maxValue ]).range([ chartH, 0 ]); // X & Y Axis xAxis = d3.axisBottom(xScale); yAxis = d3.axisLeft(yScale); - if (!colorScale) { - // If the colorScale has not already been passed - // then attempt to calculate. - colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); - } } function my(selection) { selection.each(function(data) { @@ -2298,7 +2113,7 @@ d3.ez.chart.discreteBar = function module() { chart = svg.select(".chart"); } // Update the chart dimensions - chart.classed("chartDiscreteBar", true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); + chart.classed(classed, true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); // Add axis to chart chart.select(".x-axis").attr("transform", "translate(0," + chartH + ")").call(xAxis); chart.select(".y-axis").call(yAxis); @@ -2353,21 +2168,13 @@ d3.ez.chart.discreteBar = function module() { /** * Grouped Bar Chart * - * @example - * var myChart = d3.ez.chart.groupedBar() - * .width(400) - * .height(300) - * .transition({ease: "bounce", duration: 1500}) - * .groupType("stacked"); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.groupedBar = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartGroupedBar"; var width = 400; var height = 300; var margin = { @@ -2381,29 +2188,41 @@ d3.ez.chart.groupedBar = function module() { duration: 500 }; var colors = d3.ez.colors.categorical(4); - var gap = 0; - var yAxisLabel = null; - var groupType = "clustered"; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var xScale2 = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + // Scales and Axis + var xScale; + var xScale2; + var yScale; + var xAxis; + var yAxis; + var colorScale; + // Data Variables + var groupNames; + var groupTotalsMax; + var maxValue; + var categoryNames; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = null; + var groupType = "clustered"; function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var groupNames = slicedData.groupNames; - var groupTotalsMax = slicedData.groupTotalsMax; - var maxValue = slicedData.maxValue; - var categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; + groupTotalsMax = slicedData.groupTotalsMax; + maxValue = slicedData.maxValue; + categoryNames = slicedData.categoryNames; + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand().domain(groupNames).rangeRound([ 0, chartW ]).padding(.1); yScale = d3.scaleLinear().range([ chartH, 0 ]).domain([ 0, groupType === "stacked" ? groupTotalsMax : maxValue ]); @@ -2411,8 +2230,6 @@ d3.ez.chart.groupedBar = function module() { // X & Y Axis xAxis = d3.axisBottom(xScale); yAxis = d3.axisLeft(yScale); - // Colour Scale - colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); } function my(selection) { selection.each(function(data) { @@ -2436,7 +2253,7 @@ d3.ez.chart.groupedBar = function module() { chart = selection.select(".chart"); } // Update the chart dimensions - chart.classed("chartGroupedBar", true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); + chart.classed(classed, true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); // Add axis to chart chart.select(".x-axis").attr("transform", "translate(0," + chartH + ")").call(xAxis); chart.select(".y-axis").call(yAxis); @@ -2473,16 +2290,16 @@ d3.ez.chart.groupedBar = function module() { margin = _; return this; }; - my.yAxisLabel = function(_) { - if (!arguments.length) return yAxisLabel; - yAxisLabel = _; - return this; - }; my.groupType = function(_) { if (!arguments.length) return groupType; groupType = _; return this; }; + my.yAxisLabel = function(_) { + if (!arguments.length) return yAxisLabel; + yAxisLabel = _; + return this; + }; my.transition = function(_) { if (!arguments.length) return transition; transition = _; @@ -2513,19 +2330,13 @@ d3.ez.chart.groupedBar = function module() { /** * Radial Bar Chart * - * @example - * var myChart = d3.ez.chart.radialBar(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); - * - * Credit: Peter Cook http://animateddata.co.uk/ */ d3.ez.chart.radialBar = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartRadialBar"; var width = 400; var height = 300; var margin = { @@ -2539,31 +2350,44 @@ d3.ez.chart.radialBar = function module() { duration: 500 }; var colors = d3.ez.colors.categorical(4); - var radius = undefined; - var capitalizeLabels = false; - var colorLabels = false; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var yScale = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + // Scales and Axis + var xScale; + var yScale; + var yScale2; + var colorScale; + // Data Variables var categoryNames = []; var maxValue = 0; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var capitalizeLabels = false; + var colorLabels = false; function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = typeof radius === "undefined" ? defaultRadius : radius; + innerRadius = typeof innerRadius === "undefined" ? defaultRadius / 4 : innerRadius; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - maxValue = slicedData.maxValue; - var domain = [ 0, maxValue ]; categoryNames = slicedData.categoryNames; + maxValue = slicedData.maxValue; + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand().domain(categoryNames).rangeRound([ 0, chartW ]).padding(.15); - yScale = d3.scaleLinear().domain(domain).range([ 0, radius ]); - // Colour Scale - colorScale = d3.scaleOrdinal().range(colors).domain(categoryNames); + yScale = d3.scaleLinear().domain([ 0, maxValue ]).range([ 0, radius ]); + yScale2 = d3.scaleLinear().domain([ 0, maxValue ]).range([ 0, -radius ]); } function my(selection) { selection.each(function(data) { @@ -2589,15 +2413,14 @@ d3.ez.chart.radialBar = function module() { chart = selection.select(".chart"); } // Update the chart dimensions - chart.classed("chartRadialBar", true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", width).attr("height", height); + chart.classed(classed, true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", chartW).attr("height", chartH); // Add the chart - var barRadial = d3.ez.component.barRadial().width(chartW).height(chartH).radius(radius).yScale(yScale).colorScale(colorScale).dispatch(dispatch); + var barRadial = d3.ez.component.barRadial().radius(radius).yScale(yScale).colorScale(colorScale).dispatch(dispatch); chart.select(".barChart").datum(data).call(barRadial); // Circular Axis var circularAxis = d3.ez.component.circularAxis().xScale(xScale).yScale(yScale).width(chartW).height(chartH).radius(radius); chart.select(".circleAxis").call(circularAxis); // Y Axis - var yScale2 = d3.scaleLinear().domain(yScale.domain()).range([ 0, -radius ]); var yAxis = d3.axisLeft(yScale2); chart.select(".axis").call(yAxis); // Circular Labels @@ -2609,19 +2432,16 @@ d3.ez.chart.radialBar = function module() { my.width = function(_) { if (!arguments.length) return width; width = _; - radius = d3.min([ width - (margin.right + margin.left), height - (margin.top + margin.bottom) ]) / 2; return this; }; my.height = function(_) { if (!arguments.length) return height; height = _; - radius = d3.min([ width - (margin.right + margin.left), height - (margin.top + margin.bottom) ]) / 2; return this; }; my.margin = function(_) { if (!arguments.length) return margin; margin = _; - radius = d3.min([ width - (margin.right + margin.left), height - (margin.top + margin.bottom) ]) / 2; return this; }; my.radius = function(_) { @@ -2669,19 +2489,13 @@ d3.ez.chart.radialBar = function module() { /** * Circular Heat Chart * - * @example - * var myChart = d3.ez.chart.circularHeat(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); - * - * Credit: Peter Cook http://animateddata.co.uk/ */ d3.ez.chart.circularHeat = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartCircularHeat"; var width = 400; var height = 300; var margin = { @@ -2694,36 +2508,52 @@ d3.ez.chart.circularHeat = function module() { ease: d3.easeBounce, duration: 500 }; - var classed = "chartCircularHeat"; var colors = [ d3.rgb(214, 245, 0), d3.rgb(255, 166, 0), d3.rgb(255, 97, 0), d3.rgb(200, 65, 65) ]; - var radius = undefined; - var innerRadius = undefined; - // Data Options (Populated by 'init' function) + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + // Scales and Axis + var xScale; + var yScale; + var yScale2; + var colorScale; + // Data Variables + var categoryNames = []; + var groupNames = []; var minValue = 0; var maxValue = 0; - var radialLabels = []; - var numRadials = 24; - var segmentLabels = []; - var numSegments = 24; - var segmentHeight = 0; - var colorScale = undefined; var thresholds = undefined; - var categoryNames = []; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = typeof radius === "undefined" ? defaultRadius : radius; + innerRadius = typeof innerRadius === "undefined" ? defaultRadius / 4 : innerRadius; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; // If thresholds values are not already set // attempt to auto-calculate some thresholds. if (!thresholds) { var thresholds = slicedData.thresholds; } // Colour Scale - colorScale = d3.scaleThreshold().domain(thresholds).range(colors); + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleThreshold().range(colors).domain(thresholds); + } + // X & Y Scales + xScale = d3.scaleBand().domain(categoryNames).rangeRound([ 0, chartW ]).padding(.1); + yScale = d3.scaleBand().domain(groupNames).rangeRound([ radius, innerRadius ]).padding(.1); + yScale2 = d3.scaleBand().domain(groupNames).rangeRound([ -innerRadius, -radius ]).padding(.1); } function my(selection) { selection.each(function(data) { @@ -2741,16 +2571,32 @@ d3.ez.chart.circularHeat = function module() { }(d3.select(this)); svg.classed("d3ez", true).attr("width", width).attr("height", height); chart = svg.append("g").classed("chart", true); + chart.append("g").classed("circleRings", true); + chart.append("g").classed("circleLabels", true); + chart.append("g").classed("axis", true); } else { chart = svg.select(".chart"); } // Update the chart dimensions - chart.classed("chartCircularHeat", true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", chartW).attr("height", chartH); - var heatMap = d3.ez.component.heatCircle().width(chartW).height(chartH).colorScale(colorScale).radius(radius).innerRadius(innerRadius).dispatch(dispatch); - chart.datum(data).call(heatMap); + chart.classed(classed, true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", chartW).attr("height", chartH); + var heatRing = d3.ez.component.heatRing().radius(function(d) { + return yScale(d.key); + }).innerRadius(function(d) { + return yScale(d.key) + yScale.bandwidth(); + }).colorScale(colorScale).yScale(yScale).xScale(xScale).dispatch(dispatch); + var series = chart.select(".circleRings").selectAll(".series").data(function(d) { + return d; + }).enter().append("g").attr("class", "series"); + series.datum(function(d) { + return d; + }).call(heatRing); + series.exit().remove(); // Circular Labels var circularLabels = d3.ez.component.circularLabels().width(chartW).height(chartH).radius(radius); - chart.datum(categoryNames).call(circularLabels); + chart.select(".circleLabels").datum(categoryNames).call(circularLabels); + // Y Axis + var yAxis = d3.axisLeft(yScale2); + chart.select(".axis").call(yAxis); }); } // Configuration Getters & Setters @@ -2809,38 +2655,40 @@ d3.ez.chart.circularHeat = function module() { /** * Tabular Heat Chart * - * @example - * var myChart = d3.ez.chart.tabularHeat(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.tabularHeat = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) - var width = 600; - var height = 600; + var classed = "chartTabularHeat"; + var width = 400; + var height = 300; var margin = { - top: 50, - right: 40, - bottom: 40, - left: 40 + top: 45, + right: 20, + bottom: 20, + left: 45 }; var transition = { ease: d3.easeBounce, duration: 500 }; var colors = [ d3.rgb(214, 245, 0), d3.rgb(255, 166, 0), d3.rgb(255, 97, 0), d3.rgb(200, 65, 65) ]; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + // Scales and Axis + var xScale; + var yScale; + var colorScale; + var xAxis; + var yAxis; + // Data Variables + var groupNames = []; + var categoryNames = []; + var minValue = 0; + var maxValue = 0; var thresholds = undefined; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); @@ -2849,23 +2697,27 @@ d3.ez.chart.tabularHeat = function module() { chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var minValue = slicedData.minValue; - var categoryNames = slicedData.categoryNames; - var groupNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; + categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; // If thresholds values are not already set // attempt to auto-calculate some thresholds. if (!thresholds) { var thresholds = slicedData.thresholds; } + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleThreshold().domain(thresholds).range(colors); + } // X & Y Scales xScale = d3.scaleBand().domain(categoryNames).rangeRound([ 0, chartW ]).padding(.05); yScale = d3.scaleBand().domain(groupNames).rangeRound([ 0, chartH ]).padding(.05); // X & Y Axis xAxis = d3.axisTop(xScale); yAxis = d3.axisLeft(yScale); - // Colour Scale - colorScale = d3.scaleThreshold().domain(thresholds).range(colors); } function my(selection) { selection.each(function(data) { @@ -2889,7 +2741,7 @@ d3.ez.chart.tabularHeat = function module() { chart = selection.select(".chart"); } // Update the chart dimensions - chart.classed("chartTabularHeat", true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); + chart.classed(classed, true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); // Add axis to chart chart.select(".x-axis").call(xAxis).selectAll("text").attr("y", 0).attr("x", -8).attr("transform", "rotate(60)").style("text-anchor", "end"); chart.select(".y-axis").call(yAxis); @@ -2936,11 +2788,6 @@ d3.ez.chart.tabularHeat = function module() { thresholds = _; return this; }; - my.accessor = function(_) { - if (!arguments.length) return accessor; - accessor = _; - return this; - }; my.dispatch = function(_) { if (!arguments.length) return dispatch(); dispatch = _; @@ -2956,21 +2803,13 @@ d3.ez.chart.tabularHeat = function module() { /** * Donut Chart * - * @example - * var myChart = d3.ez.chart.donut() - * .width(400) - * .height(300) - * .radius(200) - * .innerRadius(50); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.donut = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartDonut"; var width = 400; var height = 300; var margin = { @@ -2984,20 +2823,27 @@ d3.ez.chart.donut = function module() { duration: 750 }; var colors = d3.ez.colors.categorical(4); - var radius = undefined; - var innerRadius = undefined; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + // Scales and Axis + var colorScale; + // Data Variables + var categoryNames = []; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = typeof radius === "undefined" ? defaultRadius : radius; + innerRadius = typeof innerRadius === "undefined" ? defaultRadius / 2 : innerRadius; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var categoryNames = slicedData.categoryNames; + categoryNames = slicedData.categoryNames; + // Colour Scale if (!colorScale) { // If the colorScale has not already been passed // then attempt to calculate. @@ -3024,9 +2870,9 @@ d3.ez.chart.donut = function module() { chart = svg.select(".chart"); } // Update the chart dimensions - chart.classed("chartDonut", true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", chartW).attr("height", chartH); + chart.classed(classed, true).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").attr("width", chartW).attr("height", chartH); // Add the chart - var donutChart = d3.ez.component.donut().width(chartW).height(chartH).radius(radius).innerRadius(innerRadius).colorScale(colorScale).dispatch(dispatch); + var donutChart = d3.ez.component.donut().radius(radius).innerRadius(innerRadius).colorScale(colorScale).dispatch(dispatch); chart.datum(data).call(donutChart); }); } @@ -3084,81 +2930,80 @@ d3.ez.chart.donut = function module() { }; /** - * Punchcard + * Punch Card * - * @example - * var myChart = d3.ez.chart.punchCard() - * .width(600) - * .height(350) - * .color("green") - * .minRadius(5) - * .maxRadius(19) - * .useGlobalScale(true); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.punchCard = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartPunchCard"; var width = 400; var height = 300; var margin = { - top: 50, - right: 40, - bottom: 40, - left: 40 + top: 45, + right: 20, + bottom: 20, + left: 45 }; var transition = { ease: d3.easeBounce, duration: 500 }; - var color = "steelblue"; - var sizeScale = undefined; + var colors = [ d3.rgb("steelblue").brighter(), d3.rgb("steelblue").darker() ]; + // Chart Dimensions + var chartW; + var chartH; + // Scales and Axis + var sizeScale; + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; + // Data Variables + var maxValue; + var minValue; + var categoryNames; + var groupNames; + // Dispatch (Custom events) + var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Misc Options var minRadius = 2; var maxRadius = 20; var formatTick = d3.format("0000"); var useGlobalScale = true; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; - // Dispatch (Custom events) - var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var minValue = slicedData.minValue; - var categoryNames = slicedData.categoryNames; - var groupNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; + categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; valDomain = [ minValue, maxValue ]; sizeDomain = useGlobalScale ? valDomain : [ 0, d3.max(data[1]["values"], function(d) { return d["value"]; }) ]; + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleLinear().domain(valDomain).range(colors); + } // X & Y Scales xScale = d3.scaleBand().domain(categoryNames).rangeRound([ 0, chartW ]).padding(.05); yScale = d3.scaleBand().domain(groupNames).rangeRound([ 0, chartH ]).padding(.05); // X & Y Axis xAxis = d3.axisTop(xScale); yAxis = d3.axisLeft(yScale); - // Colour Scale - colorScale = d3.scaleLinear().domain(valDomain).range([ d3.rgb(color).brighter(), d3.rgb(color).darker() ]); // Size Scale sizeScale = d3.scaleLinear().domain(sizeDomain).range([ minRadius, maxRadius ]); } function my(selection) { selection.each(function(data) { - // If it is a single object, wrap it in an array - if (data.constructor !== Array) data = [ data ]; // Initialise Data init(data); // Create SVG and Chart containers (if they do not already exist) @@ -3179,7 +3024,7 @@ d3.ez.chart.punchCard = function module() { chart = selection.select(".chart"); } // Update the chart dimensions - chart.classed("chartPunchCard", true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", width).attr("height", height); + chart.classed(classed, true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); // Add axis to chart chart.select(".x-axis").call(xAxis).selectAll("text").attr("y", 0).attr("x", -8).attr("transform", "rotate(60)").style("text-anchor", "end"); chart.select(".y-axis").call(yAxis); @@ -3221,16 +3066,16 @@ d3.ez.chart.punchCard = function module() { maxRadius = _; return this; }; - my.color = function(_) { - if (!arguments.length) return color; - color = _; - return this; - }; my.sizeScale = function(_) { if (!arguments.length) return sizeScale; sizeScale = _; return this; }; + my.colors = function(_) { + if (!arguments.length) return colors; + colors = _; + return this; + }; my.useGlobalScale = function(_) { if (!arguments.length) return useGlobalScale; useGlobalScale = _; @@ -3251,19 +3096,13 @@ d3.ez.chart.punchCard = function module() { /** * Multi Series Line Chart * - * @example - * var myChart = d3.ez.chart.multiSeriesLine() - * .width(400) - * .height(300); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.multiSeriesLine = function module() { // SVG and Chart containers (Populated by 'my' function) var svg; var chart; // Default Options (Configurable via setters) + var classed = "chartMultiSeriesLine"; var width = 400; var height = 300; var margin = { @@ -3272,26 +3111,34 @@ d3.ez.chart.multiSeriesLine = function module() { bottom: 40, left: 40 }; + var transition = { + ease: d3.easeBounce, + duration: 500 + }; var colors = d3.ez.colors.categorical(3); - var yAxisLabel = null; - var groupType = "clustered"; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + // Scales and Axis + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; + // Data Variables + var maxValue; + var groupNames; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = null; function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var seriesNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + groupNames = slicedData.groupNames; // Convert dates data.forEach(function(d, i) { d.values.forEach(function(b, j) { @@ -3301,14 +3148,18 @@ d3.ez.chart.multiSeriesLine = function module() { dateDomain = d3.extent(data[0].values, function(d) { return d.key; }); + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal().range(colors).domain(groupNames); + } // X & Y Scales xScale = d3.scaleTime().range([ 0, chartW ]).domain(dateDomain); yScale = d3.scaleLinear().range([ chartH, 0 ]).domain([ 0, maxValue * 1.05 ]); // X & Y Axis xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%d-%b-%y")); yAxis = d3.axisLeft(yScale); - // Colour Scale - colorScale = d3.scaleOrdinal().range(colors).domain(seriesNames); } function my(selection) { selection.each(function(data) { @@ -3332,7 +3183,7 @@ d3.ez.chart.multiSeriesLine = function module() { chart = selection.select(".chart"); } // Update the chart dimensions - chart.classed("chartMultiSeriesLine", true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); + chart.classed(classed, true).attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", chartW).attr("height", chartH); // Add axis to chart chart.select(".x-axis").attr("transform", "translate(0," + chartH + ")").call(xAxis).selectAll("text").style("text-anchor", "end").attr("dx", "-.8em").attr("dy", ".15em").attr("transform", "rotate(-65)"); chart.select(".y-axis").call(yAxis); @@ -3370,11 +3221,6 @@ d3.ez.chart.multiSeriesLine = function module() { yAxisLabel = _; return this; }; - my.groupType = function(_) { - if (!arguments.length) return groupType; - groupType = _; - return this; - }; my.transition = function(_) { if (!arguments.length) return transition; transition = _; diff --git a/build/d3-ez.min.js b/build/d3-ez.min.js index 41076113..08cfa7b5 100644 --- a/build/d3-ez.min.js +++ b/build/d3-ez.min.js @@ -1 +1 @@ -d3.ez={version:"2.1.7",author:"James Saunders",copyright:"Copyright (C) 2017 James Saunders",license:"GPL-3.0"};d3.ez.component={description:"Reusable Components"};d3.ez.dataParse=function module(data){var levels=function(){if(data["key"]!=undefined){return 1}else{return 2}}();var groupName=function(){if(1==levels){var ret=d3.values(data)[0]}else{var ret=undefined}return ret}();var groupNames=function(){if(1==levels){ret=undefined}else{var ret=data.map(function(d){return d.key})}return ret}();var groupTotals=function(){if(1==levels){var ret=undefined}else{var ret={};d3.map(data).values().forEach(function(d,i){var groupName=d.key;d.values.forEach(function(d,i){var categoryValue=+d.value;ret[groupName]=typeof ret[groupName]==="undefined"?0:ret[groupName];ret[groupName]+=categoryValue})})}return ret}();var groupTotalsMax=function(){if(1==levels){var ret=undefined}else{var ret=d3.max(d3.values(groupTotals))}return ret}();var union=function(){var arrs=[].slice.call(arguments);var ret=[];for(var i=0,l=arrs.length;i-1){ret.splice(ret.indexOf(arrs[i][j-1])+1,0,currEl)}else{ret.push(currEl)}}}}return ret};var categoryNames=function(){if(1==levels){var ret=d3.values(data)[1].map(function(d){return d.key})}else{var ret=[];d3.map(data).values().forEach(function(d,i){var groupName=d.key;d.values.forEach(function(d,i){categoryName=d.key;ret[i]=categoryName});ret=union(ret)})}return ret}();var categoryTotal=function(){if(1==levels){var ret=d3.sum(data.values,function(d){return d.value})}else{var ret=undefined}return ret}();var categoryTotals=function(){if(1==levels){var ret=undefined}else{var ret={};d3.map(data).values().forEach(function(d,i){var groupName=d.key;d.values.forEach(function(d,i){var categoryName=d.key;var categoryValue=+d.value;ret[categoryName]=typeof ret[categoryName]==="undefined"?0:ret[categoryName];ret[categoryName]+=categoryValue})})}return ret}();var categoryTotalsMax=function(){if(1==levels){var ret=undefined}else{var ret=d3.max(d3.values(categoryTotals))}return ret}();var minValue=function(){if(1==levels){var ret=d3.min(data.values,function(d){return d.value})}else{var ret=undefined;d3.map(data).values().forEach(function(d,i){d.values.forEach(function(d,i){ret=typeof ret==="undefined"?d.value:d3.min([ret,d.value])})})}return+ret}();var maxValue=function(){if(1==levels){var ret=d3.max(data.values,function(d){return d.value})}else{var ret=undefined;d3.map(data).values().forEach(function(d,i){d.values.forEach(function(d,i){ret=typeof ret==="undefined"?d.value:d3.max([ret,d.value])})})}return+ret}();var decimalPlaces=function(num){var match=(""+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);if(!match){return 0}ret=Math.max(0,(match[1]?match[1].length:0)-(match[2]?+match[2]:0));return ret};var maxDecimalPlace=function(){if(1==levels){var ret=undefined}else{var ret=0;d3.map(data).values().forEach(function(d){d.values.forEach(function(d){ret=d3.max([ret,decimalPlaces(d.value)])})})}return ret}();var thresholds=function(){var distance=maxValue-minValue;var ret=[(minValue+.15*distance).toFixed(maxDecimalPlace),(minValue+.4*distance).toFixed(maxDecimalPlace),(minValue+.55*distance).toFixed(maxDecimalPlace),(minValue+.9*distance).toFixed(maxDecimalPlace)];return ret}();var my={levels:levels,groupName:groupName,groupNames:groupNames,groupTotals:groupTotals,groupTotalsMax:groupTotalsMax,categoryNames:categoryNames,categoryTotal:categoryTotal,categoryTotals:categoryTotals,categoryTotalsMax:categoryTotalsMax,minValue:minValue,maxValue:maxValue,maxDecimalPlace:maxDecimalPlace,thresholds:thresholds};return my};d3.ez.chart=function module(){var svg;var canvas;var width=600;var height=400;var margin={top:15,right:15,bottom:15,left:15};var canvasW=580;var canvasH=380;var chartTop=0;var classed="d3ez";var chart=undefined;var legend=undefined;var title=undefined;var creditTag=d3.ez.component.creditTag();var description="";var yAxisLabel="";var colorScale=undefined;var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function init(data){canvasW=width-(margin.left+margin.right);canvasH=height-(margin.top+margin.bottom);chart.dispatch(dispatch).width(canvasW).height(canvasH);if(legend){legend.width(150).height(200);chart.width(chart.width()-legend.width())}if(title){chartTop=title.height();chart.height(chart.height()-title.height())}creditTag.text("d3-ez.net").href("http://d3-ez.net")}function my(selection){selection.each(function(data){init(data);if(!svg){svg=d3.select(this).append("svg").classed(classed,true).attr("width",width).attr("height",height);canvas=svg.append("g").classed("canvas",true);canvas.append("g").classed("chartbox",true);canvas.append("g").classed("legendbox",true);canvas.append("g").classed("titlebox",true);canvas.append("g").classed("creditbox",true)}else{canvas=svg.select(".canvas")}canvas.attr("transform","translate("+margin.left+","+margin.top+")").attr("width",canvasW).attr("height",canvasH);canvas.select(".chartbox").datum(data).attr("transform","translate("+0+","+chartTop+")").call(chart);if(legend&&(typeof chart.colorScale==="function"||typeof chart.sizeScale==="function")){if(typeof chart.colorScale==="function"){legend.colorScale(chart.colorScale())}if(typeof chart.sizeScale==="function"){legend.sizeScale(chart.sizeScale())}canvas.select(".legendbox").attr("transform","translate("+(canvasW-legend.width())+","+title.height()+")").call(legend)}if(title){canvas.select(".titlebox").attr("transform","translate("+width/2+","+0+")").call(title)}canvas.select(".creditbox").attr("transform","translate("+(width-20)+","+(height-20)+")").call(creditTag)})}my.width=function(_){if(!arguments.length)return width;width=_;return this};my.height=function(_){if(!arguments.length)return height;height=_;return this};my.chart=function(_){if(!arguments.length)return chart;chart=_;return this};my.legend=function(_){if(!arguments.length)return legend;legend=_;return this};my.title=function(_){if(!arguments.length)return title;if(typeof _==="string"){title=d3.ez.title().mainText(_).subText("")}else{title=_}return this};my.yAxisLabel=function(_){if(!arguments.length)return yAxisLabel;yAxisLabel=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.vega=function module(){var svg;var canvas;var width=1e3;var height=600;var margin={top:30,right:30,bottom:30,left:30};var padding=20;var canvasW=580;var canvasH=380;var chartTop=0;var classed="d3ez";var chart=undefined;var yScale=undefined;var yAxis=undefined;var xScale=undefined;var xAxis=undefined;var legend=undefined;var title=undefined;var creditTag=d3.ez.component.creditTag();var yAxisLabel="";var colorScale=undefined;var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function init(data){canvasW=width-(margin.left+margin.right);canvasH=height-(margin.top+margin.bottom);chart.colorScale(colorScale).xScale(xScale).yScale(yScale).width(canvasW).height(canvasH).dispatch(dispatch);if(legend){legend.width(120).height(200);chart.width(chart.width()-legend.width()-padding)}if(title){chartTop=title.height()+padding;chart.height(chart.height()-title.height()-padding)}xScale.range([chart.width(),0]);yScale.range([chart.height(),0]);xAxis=d3.axisBottom(xScale);yAxis=d3.axisLeft(yScale);creditTag.text("d3-ez.net").href("http://d3-ez.net")}function my(selection){selection.each(function(data){init(data);if(!svg){svg=d3.select(this).append("svg").classed(classed,true).attr("width",width).attr("height",height);canvas=svg.append("g").classed("canvas",true);canvas.append("g").classed("chartbox",true);canvas.select(".chartbox").append("g").classed("x-axis axis",true);canvas.select(".chartbox").append("g").classed("y-axis axis",true);canvas.append("g").classed("legendbox",true);canvas.append("g").classed("titlebox",true);svg.append("g").classed("creditbox",true)}else{canvas=svg.select(".canvas")}canvas.attr("transform","translate("+margin.left+","+margin.top+")").attr("width",canvasW).attr("height",canvasH);canvas.select(".chartbox").attr("transform","translate(0,"+chartTop+")").datum(data).call(chart);canvas.select(".y-axis").call(yAxis);canvas.select(".x-axis").attr("transform","translate(0,"+chart.height()+")").call(xAxis);if(title){canvas.select(".titlebox").attr("transform","translate("+width/2+","+0+")").call(title)}if(legend&&(typeof chart.colorScale==="function"||typeof chart.sizeScale==="function")){if(typeof chart.colorScale==="function"){legend.colorScale(chart.colorScale())}if(typeof chart.sizeScale==="function"){legend.sizeScale(chart.sizeScale())}canvas.select(".legendbox").attr("transform","translate("+(canvasW-legend.width())+","+chartTop+")").call(legend)}if(creditTag){svg.select(".creditbox").attr("transform","translate("+(width-10)+","+(height-5)+")").call(creditTag)}})}my.width=function(_){if(!arguments.length)return width;width=_;return this};my.height=function(_){if(!arguments.length)return height;height=_;return this};my.chart=function(_){if(!arguments.length)return chart;chart=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return this};my.xScale=function(_){if(!arguments.length)return xScale;xScale=_;return this};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return this};my.legend=function(_){if(!arguments.length)return legend;legend=_;return this};my.title=function(_){if(!arguments.length)return title;if(typeof _==="string"){title=d3.ez.title().mainText(_).subText("")}else{title=_}return this};my.yAxisLabel=function(_){if(!arguments.length)return yAxisLabel;yAxisLabel=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.colors={categorical:function(scheme){switch(scheme){case 1:return["#5da5da","#faa43a","#60bd68","#f17cb0","#b2912f","#b276b2","#decf3f","#f15854","#4d4d4d"];case 2:return["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"];case 3:return["#3f51b5","#ff9800","#8bc34a","#9c27b0","#ffeb3b","#03a9f4","#f44336","#009688","#795548"];case 4:return d3.ez.colors.lumShift(d3.ez.colors.lumShift(d3.ez.colors.categorical(3),-.8),5.5)}},diverging:function(scheme){switch(scheme){case 1:return["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"];case 2:return["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"];case 3:return["#0000ff","#8052fe","#b58bfb","#ddc5f7","#fffff0","#ffcfb4","#ff9e7a","#ff6842","#ff0000"]}},sequential:function(origHex,count){var lumStep=.1;var lumMax=lumStep*count/2;var lumMin=0-lumMax;var lumScale=d3.scale.linear().domain([1,count]).range([lumMin,lumMax]);var result=[];for(var i=1;i<=count;i++){lum=lumScale(i);origHex=String(origHex).replace(/[^0-9a-f]/gi,"");if(origHex.length<6){origHex=origHex[0]+origHex[0]+origHex[1]+origHex[1]+origHex[2]+origHex[2]}var newHex="#";var c;for(var j=0;j<3;j++){c=parseInt(origHex.substr(j*2,2),16);c=Math.round(Math.min(Math.max(0,c+c*lum),255)).toString(16);newHex+=("00"+c).substr(c.length)}result.push(newHex)}return result},lumShift:function(colors,lum){var result=[];colors.forEach(function addNumber(origHex,index){origHex=String(origHex).replace(/[^0-9a-f]/gi,"");if(origHex.length<6){origHex=origHex[0]+origHex[0]+origHex[1]+origHex[1]+origHex[2]+origHex[2]}lum=lum||0;var newHex="#",c,i;for(i=0;i<3;i++){c=parseInt(origHex.substr(i*2,2),16);c=Math.round(Math.min(Math.max(0,c+c*lum),255)).toString(16);newHex+=("00"+c).substr(c.length)}result[index]=newHex});return result}};d3.ez.component.barGrouped=function module(){var height=100;var width=300;var colorScale=undefined;var xScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function my(selection){selection.each(function(data){var barW=xScale.bandwidth();selection.selectAll(".barGrouped").data(function(d){return[d]}).enter().append("g").classed("barGrouped",true).attr("width",width).attr("height",height).on("click",function(d){dispatch.call("customClick",this,d)});var barGroup=selection.selectAll(".barGrouped");var bars=barGroup.selectAll(".bar").data(function(d){return d.values});bars.enter().append("rect").classed("bar",true).attr("fill",function(d){return colorScale(d.key)}).attr("width",barW).attr("x",function(d,i){return xScale(d.key)}).attr("y",height).attr("rx",0).attr("ry",0).attr("height",0).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(bars).transition().ease(transition.ease).duration(transition.duration).attr("x",function(d,i){return xScale(d.key)}).attr("y",function(d,i){return yScale(d.value)}).attr("height",function(d,i){return height-yScale(d.value)});bars.exit().transition().style("opacity",0).remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.xScale=function(_){if(!arguments.length)return xScale;xScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.barStacked=function module(){var height=100;var width=50;var colorScale=undefined;var xScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function my(selection){selection.each(function(data){selection.selectAll(".barStacked").data(function(d){series=[];var y0=0;d3.map(d.values).values().forEach(function(d,i){series[i]={name:d.key,value:d.value,y0:y0,y1:y0+d.value};y0+=d.value});data={key:data.key,values:series};return[data]}).enter().append("g").classed("barStacked",true).attr("width",width).attr("height",height).on("click",function(d){dispatch.call("customClick",this,d)});var barGroup=selection.selectAll(".barStacked");var bars=barGroup.selectAll(".bar").data(function(d){return d.values});bars.enter().append("rect").classed("bar",true).attr("width",width).attr("x",0).attr("y",height).attr("rx",0).attr("ry",0).attr("height",0).attr("fill",function(d){return colorScale(d.name)}).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(bars).transition().ease(transition.ease).duration(transition.duration).attr("width",width).attr("x",0).attr("y",function(d){return yScale(d.y1)}).attr("height",function(d){return yScale(d.y0)-yScale(d.y1)});bars.exit().transition().style("opacity",0).remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.xScale=function(_){if(!arguments.length)return xScale;xScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.barRadial=function module(){var width=400;var height=300;var colorScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");var radius=undefined;var capitalizeLabels=false;var colorLabels=false;function my(selection){selection.each(function(data){var defaultRadius=Math.min(width,height)/2;radius=typeof radius==="undefined"?defaultRadius:radius;var yDomain=yScale.domain();yDomain[1]=yDomain[1]*1.05;var barScale=d3.scaleLinear().domain(yDomain).range([0,radius]);var axisScale=d3.scaleLinear().domain(yDomain).range([0,-radius]);var arc=d3.arc().innerRadius(0).outerRadius(function(d,i){return barScale(d.value)}).startAngle(function(d,i,j){numBars=j.length;return i*2*Math.PI/numBars}).endAngle(function(d,i,j){numBars=j.length;return(i+1)*2*Math.PI/numBars});var barRadial=selection.selectAll(".barRadial").data(function(d){return[d]}).enter().append("g").classed("barRadial",true).on("click",function(d){dispatch.call("customClick",this,d)});var barRadial=selection.selectAll(".barRadial").merge(barRadial);var segments=barRadial.selectAll("path").data(function(d){return d.values});segments.enter().append("path").style("fill",function(d,i){return colorScale(d.key)}).classed("segment",true).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(segments).transition().ease(transition.ease).duration(transition.duration).attr("d",arc);segments.exit().remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.radius=function(_){if(!arguments.length)return radius;radius=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.donut=function module(){var height=100;var width=300;var colorScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");var radius=undefined;var innerRadius=undefined;function my(selection){selection.each(function(data){var defaultRadius=Math.min(width,height)/2;radius=typeof radius==="undefined"?defaultRadius:radius;innerRadius=typeof innerRadius==="undefined"?defaultRadius/2:innerRadius;var pie=d3.pie().value(function(d){return d.value}).sort(null);var arc=d3.arc().innerRadius(innerRadius).outerRadius(radius).cornerRadius(3).padAngle(.015);var outerArc=d3.arc().innerRadius(radius*.9).outerRadius(radius*.9);function arcTween(d){var i=d3.interpolate(this._current,d);this._current=i(0);return function(t){return arc(i(t))}}function midAngle(d){return d.startAngle+(d.endAngle-d.startAngle)/2}var chartDonut=selection.selectAll(".donut").data(function(d){return[d]}).enter().append("g").classed("donut",true).on("click",function(d){dispatch.call("customClick",this,d)});chartDonut.append("g").attr("class","slices");chartDonut.append("g").attr("class","labels");chartDonut.append("g").attr("class","lines");var chartDonut=selection.selectAll(".donut");var slices=chartDonut.select(".slices").selectAll("path.slice").data(function(d){return pie(d.values)});slices.enter().append("path").attr("class","slice").attr("fill",function(d){return colorScale(d.data.key)}).attr("d",arc).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(slices).transition().duration(transition.duration).ease(transition.ease).attrTween("d",arcTween);slices.exit().remove();var labels=chartDonut.select(".labels").selectAll("text.label").data(function(d){return pie(d.values)});labels.enter().append("text").attr("class","label").attr("dy",".35em").merge(labels).transition().duration(transition.duration).text(function(d,i){return d.data.key}).attrTween("transform",function(d){this._current=this._current||d;var interpolate=d3.interpolate(this._current,d);this._current=interpolate(0);return function(t){var d2=interpolate(t);var pos=outerArc.centroid(d2);pos[0]=radius*(midAngle(d2) "+min:min+" - "+max;return rangeStr;break}var rangeIncrement=domainSize/rangeLength;var ranges=[];var range=[];var rangeStart=domainMin;var rangeEnd=domainMin+rangeIncrement;for(i=0;i-1){ret.splice(ret.indexOf(arrs[i][j-1])+1,0,currEl)}else{ret.push(currEl)}}}}return ret};var categoryNames=function(){if(1==levels){var ret=d3.values(data)[1].map(function(d){return d.key})}else{var ret=[];d3.map(data).values().forEach(function(d,i){var groupName=d.key;d.values.forEach(function(d,i){categoryName=d.key;ret[i]=categoryName});ret=union(ret)})}return ret}();var categoryTotal=function(){if(1==levels){var ret=d3.sum(data.values,function(d){return d.value})}else{var ret=undefined}return ret}();var categoryTotals=function(){if(1==levels){var ret=undefined}else{var ret={};d3.map(data).values().forEach(function(d,i){var groupName=d.key;d.values.forEach(function(d,i){var categoryName=d.key;var categoryValue=+d.value;ret[categoryName]=typeof ret[categoryName]==="undefined"?0:ret[categoryName];ret[categoryName]+=categoryValue})})}return ret}();var categoryTotalsMax=function(){if(1==levels){var ret=undefined}else{var ret=d3.max(d3.values(categoryTotals))}return ret}();var minValue=function(){if(1==levels){var ret=d3.min(data.values,function(d){return d.value})}else{var ret=undefined;d3.map(data).values().forEach(function(d,i){d.values.forEach(function(d,i){ret=typeof ret==="undefined"?d.value:d3.min([ret,d.value])})})}return+ret}();var maxValue=function(){if(1==levels){var ret=d3.max(data.values,function(d){return d.value})}else{var ret=undefined;d3.map(data).values().forEach(function(d,i){d.values.forEach(function(d,i){ret=typeof ret==="undefined"?d.value:d3.max([ret,d.value])})})}return+ret}();var decimalPlaces=function(num){var match=(""+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);if(!match){return 0}ret=Math.max(0,(match[1]?match[1].length:0)-(match[2]?+match[2]:0));return ret};var maxDecimalPlace=function(){if(1==levels){var ret=undefined}else{var ret=0;d3.map(data).values().forEach(function(d){d.values.forEach(function(d){ret=d3.max([ret,decimalPlaces(d.value)])})})}return ret}();var thresholds=function(){var distance=maxValue-minValue;var ret=[(minValue+.15*distance).toFixed(maxDecimalPlace),(minValue+.4*distance).toFixed(maxDecimalPlace),(minValue+.55*distance).toFixed(maxDecimalPlace),(minValue+.9*distance).toFixed(maxDecimalPlace)];return ret}();var my={levels:levels,groupName:groupName,groupNames:groupNames,groupTotals:groupTotals,groupTotalsMax:groupTotalsMax,categoryNames:categoryNames,categoryTotal:categoryTotal,categoryTotals:categoryTotals,categoryTotalsMax:categoryTotalsMax,minValue:minValue,maxValue:maxValue,maxDecimalPlace:maxDecimalPlace,thresholds:thresholds};return my};d3.ez.chart=function module(){var svg;var canvas;var width=600;var height=400;var margin={top:15,right:15,bottom:15,left:15};var canvasW=580;var canvasH=380;var chartTop=0;var classed="d3ez";var chart=undefined;var legend=undefined;var title=undefined;var creditTag=d3.ez.component.creditTag();var description="";var yAxisLabel="";var colorScale=undefined;var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function init(data){canvasW=width-(margin.left+margin.right);canvasH=height-(margin.top+margin.bottom);chart.dispatch(dispatch).width(canvasW).height(canvasH);if(legend){legend.width(150).height(200);chart.width(chart.width()-legend.width())}if(title){chartTop=title.height();chart.height(chart.height()-title.height())}creditTag.text("d3-ez.net").href("http://d3-ez.net")}function my(selection){selection.each(function(data){init(data);if(!svg){svg=d3.select(this).append("svg").classed(classed,true).attr("width",width).attr("height",height);canvas=svg.append("g").classed("canvas",true);canvas.append("g").classed("chartbox",true);canvas.append("g").classed("legendbox",true);canvas.append("g").classed("titlebox",true);canvas.append("g").classed("creditbox",true)}else{canvas=svg.select(".canvas")}canvas.attr("transform","translate("+margin.left+","+margin.top+")").attr("width",canvasW).attr("height",canvasH);canvas.select(".chartbox").datum(data).attr("transform","translate("+0+","+chartTop+")").call(chart);if(legend&&(typeof chart.colorScale==="function"||typeof chart.sizeScale==="function")){if(typeof chart.colorScale==="function"){legend.colorScale(chart.colorScale())}if(typeof chart.sizeScale==="function"){legend.sizeScale(chart.sizeScale())}canvas.select(".legendbox").attr("transform","translate("+(canvasW-legend.width())+","+title.height()+")").call(legend)}if(title){canvas.select(".titlebox").attr("transform","translate("+width/2+","+0+")").call(title)}canvas.select(".creditbox").attr("transform","translate("+(width-20)+","+(height-20)+")").call(creditTag)})}my.width=function(_){if(!arguments.length)return width;width=_;return this};my.height=function(_){if(!arguments.length)return height;height=_;return this};my.chart=function(_){if(!arguments.length)return chart;chart=_;return this};my.legend=function(_){if(!arguments.length)return legend;legend=_;return this};my.title=function(_){if(!arguments.length)return title;if(typeof _==="string"){title=d3.ez.title().mainText(_).subText("")}else{title=_}return this};my.yAxisLabel=function(_){if(!arguments.length)return yAxisLabel;yAxisLabel=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.colors={categorical:function(scheme){switch(scheme){case 1:return["#5da5da","#faa43a","#60bd68","#f17cb0","#b2912f","#b276b2","#decf3f","#f15854","#4d4d4d"];case 2:return["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"];case 3:return["#3f51b5","#ff9800","#8bc34a","#9c27b0","#ffeb3b","#03a9f4","#f44336","#009688","#795548"];case 4:return d3.ez.colors.lumShift(d3.ez.colors.lumShift(d3.ez.colors.categorical(3),-.8),5.5)}},diverging:function(scheme){switch(scheme){case 1:return["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"];case 2:return["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"];case 3:return["#0000ff","#8052fe","#b58bfb","#ddc5f7","#fffff0","#ffcfb4","#ff9e7a","#ff6842","#ff0000"]}},sequential:function(origHex,count){var lumStep=.1;var lumMax=lumStep*count/2;var lumMin=0-lumMax;var lumScale=d3.scale.linear().domain([1,count]).range([lumMin,lumMax]);var result=[];for(var i=1;i<=count;i++){lum=lumScale(i);origHex=String(origHex).replace(/[^0-9a-f]/gi,"");if(origHex.length<6){origHex=origHex[0]+origHex[0]+origHex[1]+origHex[1]+origHex[2]+origHex[2]}var newHex="#";var c;for(var j=0;j<3;j++){c=parseInt(origHex.substr(j*2,2),16);c=Math.round(Math.min(Math.max(0,c+c*lum),255)).toString(16);newHex+=("00"+c).substr(c.length)}result.push(newHex)}return result},lumShift:function(colors,lum){var result=[];colors.forEach(function addNumber(origHex,index){origHex=String(origHex).replace(/[^0-9a-f]/gi,"");if(origHex.length<6){origHex=origHex[0]+origHex[0]+origHex[1]+origHex[1]+origHex[2]+origHex[2]}lum=lum||0;var newHex="#",c,i;for(i=0;i<3;i++){c=parseInt(origHex.substr(i*2,2),16);c=Math.round(Math.min(Math.max(0,c+c*lum),255)).toString(16);newHex+=("00"+c).substr(c.length)}result[index]=newHex});return result}};d3.ez.component.barGrouped=function module(){var height=100;var width=300;var colorScale=undefined;var xScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function my(selection){selection.each(function(data){var barW=xScale.bandwidth();selection.selectAll(".barGrouped").data(function(d){return[d]}).enter().append("g").classed("barGrouped",true).attr("width",width).attr("height",height).on("click",function(d){dispatch.call("customClick",this,d)});var barGroup=selection.selectAll(".barGrouped");var bars=barGroup.selectAll(".bar").data(function(d){return d.values});bars.enter().append("rect").classed("bar",true).attr("fill",function(d){return colorScale(d.key)}).attr("width",barW).attr("x",function(d,i){return xScale(d.key)}).attr("y",height).attr("rx",0).attr("ry",0).attr("height",0).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(bars).transition().ease(transition.ease).duration(transition.duration).attr("x",function(d,i){return xScale(d.key)}).attr("y",function(d,i){return yScale(d.value)}).attr("height",function(d,i){return height-yScale(d.value)});bars.exit().transition().style("opacity",0).remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.xScale=function(_){if(!arguments.length)return xScale;xScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.barStacked=function module(){var height=100;var width=50;var colorScale=undefined;var xScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");function my(selection){selection.each(function(data){selection.selectAll(".barStacked").data(function(d){series=[];var y0=0;d3.map(d.values).values().forEach(function(d,i){series[i]={name:d.key,value:d.value,y0:y0,y1:y0+d.value};y0+=d.value});data={key:data.key,values:series};return[data]}).enter().append("g").classed("barStacked",true).attr("width",width).attr("height",height).on("click",function(d){dispatch.call("customClick",this,d)});var barGroup=selection.selectAll(".barStacked");var bars=barGroup.selectAll(".bar").data(function(d){return d.values});bars.enter().append("rect").classed("bar",true).attr("width",width).attr("x",0).attr("y",height).attr("rx",0).attr("ry",0).attr("height",0).attr("fill",function(d){return colorScale(d.name)}).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(bars).transition().ease(transition.ease).duration(transition.duration).attr("width",width).attr("x",0).attr("y",function(d){return yScale(d.y1)}).attr("height",function(d){return yScale(d.y0)-yScale(d.y1)});bars.exit().transition().style("opacity",0).remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.xScale=function(_){if(!arguments.length)return xScale;xScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.barRadial=function module(){var width=400;var height=300;var colorScale=undefined;var yScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");var radius=undefined;var capitalizeLabels=false;var colorLabels=false;function my(selection){selection.each(function(data){var defaultRadius=Math.min(width,height)/2;radius=typeof radius==="undefined"?defaultRadius:radius;var yDomain=yScale.domain();yDomain[1]=yDomain[1]*1.05;var barScale=d3.scaleLinear().domain(yDomain).range([0,radius]);var axisScale=d3.scaleLinear().domain(yDomain).range([0,-radius]);var arc=d3.arc().innerRadius(0).outerRadius(function(d,i){return barScale(d.value)}).startAngle(function(d,i,j){numBars=j.length;return i*2*Math.PI/numBars}).endAngle(function(d,i,j){numBars=j.length;return(i+1)*2*Math.PI/numBars});var barRadial=selection.selectAll(".barRadial").data(function(d){return[d]}).enter().append("g").classed("barRadial",true).on("click",function(d){dispatch.call("customClick",this,d)});var barRadial=selection.selectAll(".barRadial").merge(barRadial);var segments=barRadial.selectAll("path").data(function(d){return d.values});segments.enter().append("path").style("fill",function(d,i){return colorScale(d.key)}).classed("segment",true).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(segments).transition().ease(transition.ease).duration(transition.duration).attr("d",arc);segments.exit().remove()})}my.height=function(_){if(!arguments.length)return height;height=_;return this};my.width=function(_){if(!arguments.length)return width;width=_;return this};my.radius=function(_){if(!arguments.length)return radius;radius=_;return this};my.colorScale=function(_){if(!arguments.length)return colorScale;colorScale=_;return my};my.yScale=function(_){if(!arguments.length)return yScale;yScale=_;return my};my.dispatch=function(_){if(!arguments.length)return dispatch();dispatch=_;return this};my.on=function(){var value=dispatch.on.apply(dispatch,arguments);return value===dispatch?my:value};return my};d3.ez.component.donut=function module(){var height=100;var width=300;var colorScale=undefined;var transition={ease:d3.easeBounce,duration:500};var dispatch=d3.dispatch("customMouseOver","customMouseOut","customClick");var radius=undefined;var innerRadius=undefined;function my(selection){selection.each(function(data){var defaultRadius=Math.min(width,height)/2;radius=typeof radius==="undefined"?defaultRadius:radius;innerRadius=typeof innerRadius==="undefined"?defaultRadius/2:innerRadius;var pie=d3.pie().value(function(d){return d.value}).sort(null).padAngle(.015);var arc=d3.arc().innerRadius(innerRadius).outerRadius(radius).cornerRadius(2);var outerArc=d3.arc().innerRadius(radius*.9).outerRadius(radius*.9);function arcTween(d){var i=d3.interpolate(this._current,d);this._current=i(0);return function(t){return arc(i(t))}}function midAngle(d){return d.startAngle+(d.endAngle-d.startAngle)/2}var chartDonut=selection.selectAll(".donut").data(function(d){return[d]}).enter().append("g").classed("donut",true).on("click",function(d){dispatch.call("customClick",this,d)});chartDonut.append("g").attr("class","slices");chartDonut.append("g").attr("class","labels");chartDonut.append("g").attr("class","lines");var chartDonut=selection.selectAll(".donut");var slices=chartDonut.select(".slices").selectAll("path.slice").data(function(d){return pie(d.values)});slices.enter().append("path").attr("class","slice").attr("fill",function(d){return colorScale(d.data.key)}).attr("d",arc).on("mouseover",function(d){dispatch.call("customMouseOver",this,d)}).merge(slices).transition().duration(transition.duration).ease(transition.ease).attrTween("d",arcTween);slices.exit().remove();var labels=chartDonut.select(".labels").selectAll("text.label").data(function(d){return pie(d.values)});labels.enter().append("text").attr("class","label").attr("dy",".35em").merge(labels).transition().duration(transition.duration).text(function(d,i){return d.data.key}).attrTween("transform",function(d){this._current=this._current||d;var interpolate=d3.interpolate(this._current,d);this._current=interpolate(0);return function(t){var d2=interpolate(t);var pos=outerArc.centroid(d2);pos[0]=radius*(midAngle(d2) "+min:min+" - "+max;return rangeStr;break}var rangeIncrement=domainSize/rangeLength;var ranges=[];var range=[];var rangeStart=domainMin;var rangeEnd=domainMin+rangeIncrement;for(i=0;i=5_yhd6>yhd7ecnbgl1oZ&`00a~O008VgX?NR5j^FbuYLtB)Dwb(V zvK(7!Zzi$3(>*WU?Ts@%Z=c7TN0B9o4n=B6%C<7ee;=SO7JrMRE+0A8nj>T#017~% z093KO^YrN>@|5`9ZTb-&@cFOqEE~rO`I|eTDLHj#kx!G9oL%7iUsLhEJ#;5QIJYC3 zowHtEJe?=OXq=HF^<*72NylkFGhngyVc^jyrR4R=o69bg>^$aK)bq67ZF^h(&-raXI_ry(Ll!s(Ei#+T_Cb2&YsfBe-IaW6zA-$ww zsx`D^E&hTbVLi?oALzVsPX2O8a1_HJqQ2e^e+kWINq##^*xs$FS=KJ~z^P7A91$2h#$hDQ5292WT z`s0~JXkTlFfeqXw!Kra}Zl`GWZO;uu%S}eJ34dB{4Pj2=nIv$!e@T=#OOl_ozGoK|`h*e# z>g@moa{b59;+>jIJGa|)lLC8PkZsb2#Rc1uAATS@6onDnoHxmTFMuUPWv5f*v$Y7! z7D8#akRyV~>e*4LM<%lAEFD{m!IWpwqw2Q>8kg&voS`>aVComh_WA8CAd9ErnV~~p zv+34(UJqNa2K0;^7P>6AlRX0)e^E^;mbf((WphWYz<>w$WOessxt?$4%;@k9f5xb) zb!iIiR>gELj!jY9z7pT2NYn=I%_NAVd~a+&0M^%GKk#?MxwzRR;}qp;U!mv%FsQrW z*cmo#sq*uRrDHAa-Kr6^dwqL?Mjd<*^g4nDj@^@>ed+}hH#`YlujoKVf3t}hyG-27 z8$%nb(Ey(r8`!MK+Ob~r|7lzQ^tYPL7w~*We>%6$wzkh-@S^|P(I`XJ`3vh;lQnW` zMgf?AbF(qy4tVQ3JLLD-WIz)FO!b3NkfnrY+wh$ai%jDn%4`M2VtB!?1&+%{5&Hn= zohAeXqDcpvY^i6g)W)#Deo1ipz+dYd?|HjI z%-t`(mRm6Z{o-p)tJG*#2aS_gD#}|+S@qSERwq=lubv8Zx&-@XLEbW-34L>-2wNy5 zK7o({cO!QAF3-`}e@!y-%1x=<>Y>-}WExWToW*{e@59(0Yh2#9kP{9j`{|#ri5vOk z$c--Dlz4HJxnL}llvpS6l+9+J4Cds?Wd20KX)4W2FGsq|jxdxRIi^E*7G~rRw$lOt zkK$+;jAjYjZMh6wG$I+G%E4U)eufL=p5x?T$CNEfhh$eTf1S9=D2NJrpT*NYY4>s_ zi+v=$Xb@*vJSmoibjVtnD-j>hKLAH>-!Y4Sf#PnV7@7Spp5_Y(D-GQ=1r~z4;&WaRL+6Y4eHR9$kuY>fBJD{Of z0Wv*`!+4hDe+J!y_v91|e+5aX0zo=uK9A+iQs6rQi-cL)oC4W^Q7qrA25z9dR`ULinuB!QopzSozSj8C8~`vT1W(sW^eahwhiY1&j47^3m^Nuxy`-y>D9I1~1<5M*Tu9at zn+62tK`sqBGwh6BXI6vlqvM`<1GtpXq16~?*|fj2qg9ACxTtcIxs}q8ato=AQx)2@ z=m8qqf5{l7)V9a~oON2eE*!Fd9Dw2MwJw3X@j3Xw4#E3>v^%S9zUK zEHaXeQY#w=Y14M6QxLY*fLh>$uw$0^w?(o9JWv~4z{F}Wg@75!{*{GX9#G03DqK~r z*nk>PD0Diq25-?2*yU ze9-=8C1YiniRgt-^2Vf2VbvoWquK!bE94ALUZ6`v8T-nz^td3So=( zYaW7-AIK?xV$CLVTZSTPxqfeEpZ=mw9f%BAQ^stpZ%%>)^}ZC&<4RjdfB|9^?regY zch2!BV5{La1IBewv%Z?oEI_S>SVcUWVCJ23JPO!qxMf7O32N52n3@$v?mahae`%Hk z(Wqq9WSR}v+AyRLzp#wiKwFea~!`b9?91JsS0rnPg><;Uz z(oC$GO2uf+`?&~GZwx_;E(o;6c~h230G9A}JmHM~yLptaV*rU^R4~BxxySv>kXS3rR?Co>qeRk06 z?e14Hbhlqc%nH_Sxee@s4cxZ(;s1Mfuas{3%oEGXBrmgp$FgCS1!VRP51#&O=7I&! zz-hgt{A4=$dpZN(VNSBE7_b4`63hvWXEfEpGt(|RTnYUnye@bDcOAik|4d};1=aUz| zIdtjY)A429|J?GJ{0JihPshR@W1hjONSf5XA9vquSWH3obztHOzDC{iCjLCMYdQ{D}X%bA_B!D?3{R|p;u}_WJC+3=`yAlBV zfAmJ$wz-=yJL_xYhVvGmi)dZ}%@`(R4vTF8ffWJ)fKz2_9|4IPei}|TdCEtMF?_it z+ahx?e_z46l^V#s8_fRVqYaahx(UP$evFZWb%tlVIfC=1%|-%c9CS+(qaQ@zyYeaS z{4F?o-)1N64>{z@nIwD?^Gp$OeV<1mxe;WaY?z|?uBU>s8<;wGN=D;WrU_^su(o1m1bkKm zd{$IgH733Vvm*GGXT=%QHav>X0hY6{f4psZYUsHU^+E1y4Blbgd zEz*iZ4+A$5Fsvpv#-F}(@$~6&m+%$v50+1R`cx%FWWri(e`0!><1tbnWn7C7+-z_v zMkR6E`T-c3RqpDAi_T+U{q=&n`GPu@mJh^1kzAvxi(g$ME8yW3Xrz9`$rm~He?I9s zj-C>`(}N$ku?vp`f;{n{2xk~bH}b_+Pu<2mPk6J0Q=vtUm_NM)O=@WB!uKTI)jkqC zBR(;$U(*Ktqv*0#ZYFD(HrTbdfuM%RIKAxLxk{$8$x;wfi{)rrq_p{EybH!NdT|?j zDeEftUYRuj`~JEl3jrT(g@j^)K3+>4@_6s!Q6a{Xj1oXmR%Q+)SSW>c&=B#G1s(*mn4Y7QEzjT<`FV~tHPU0c>Gv+4MfH0Rl%xl z)+O+(l>Iu$EQ7Z!J83oIsq2Az#RZwUPP8!uqo ziyJTLyVl)Uu8)TiZW0@D5cd)q z32JXUGLjQ}xAL>!wKh1u>F|eb_yUV(Y0U?|?lj2UF!)H>VQ6-Vf*YpPU{6)$t?!Tc zT+DBOCAn%Rk_l6JLB&%kY(mH$@;VpsUcS+#fT1rMmfh3&F*~UO5V`Q>$rWSm70qy8 z`A~m(O4_#5tMq!!!!d7+voQinek{gm$>Eq+bFix`uY+_GklQsd(Ya1MSoR|dN*X6+ zgo(#d!2_TOB526jX<@c~mZ{fObHb7-^~vUcGy^O4G8#df0?-z@sDc~KCc?5^$U8La zaI>v(_#e}>lV3rzMCXd=%fAx}tN^dNY_3X2Nm@(N5kl*fk*y83c#C`3I24Fv6!aw` z&o#KAq7YtE=Km=ju@$|9ClwVu3gz8(G%%L&9fYA0ZAlISpNI-dLE+*#x_mECiC7SS zP!RC_#fx(Iw-z&EJ_Sr&Eth%V^$=IxD6UfL_a>OlHCzGvmSVbx$m&~&td8SomTeYP z`PlXD11g5*Yd$UZ%?C`H$6IdjorXlkF$;)=!X;qwE`Yw?qvS`O1av{vF%3k30}qzi z)LP4HD#sLYl3CF#44d}U6|TMQw0lc`;-Rviw;&+Wo{OPTBFelt$y;|SoK@E8WjzKV z)u+ThsKUT2ieU4T-HG?^iqabP$&V@kZGfKSG-fv_p!K%j<8AsBKjt$1F#Hvo0EECY z?}O6Bqhy#>5ur82u?Ntq>Ou}fxYfg*1VYM-wIz==@mdI9#eHPEP+2kK%3n`^EDz0C z-LbIQ)zJvE=yx5LaU@?PCd*k+AdP$9j6gKh><@F4E*x5|Z^&|28}fiv!5&pc6;I?XDaOd#mApd|h}cREei$OR5ygY;Bb)$?2f05?)isEAC2_IM$LX zA>x`DtBQTxylmLr9-XL+3*8!gMsIqet_t8V0zji)gxq;zO(59mqaz_)zU?~y;~fS+=I9TMfsSos>> zX_6mRw)6#Q+vs0zx9vJ$VqC^uP)HNI>}k1EAu?xD@SscEZsd*Q<(B9Fx9DX=T_?E) z^Qjsjy#^SBRRmFOSC$w`w&vn-A%5i#Zjn@&O)Ramn%og6tiN$M$6{7@XLb|umRDeb zy4qpMtt56{%SdX*josIOkUpsQeONa8pm!_w?F{{i0tKyV!di^#io>PLrW;qshphKE zp7kzfbT%QNGMQ5Z`z(Ig?GiP6gZ7A7YtWH)gAv z1vh8c=gK)nHQ93gcKh4SnByo2yP#i^nP-r399UNx$%;8-^Irac%Iw2q)k1nEXT-tl zR;uzq+=mF*#YbAd=T#{A!15c;jYy7(5C2b{uZ1P!Lm$VC=a}K2n*5Tv9A5>IA79xq zK(|=8@+u?5^Ol+Wzd=MF4Ce$1EYN*@!q#WJutrmo0)LE17)qkp$3*jpX0Kkw*Os_P zU$ZZY8Ov{+Wdz`VO|!&lHa1)!g+6U`pD1+A2^WA?yf>C>>PCw)bk&w*4rm3e)uxAe z?bIi~gIH>f4^~!~o{;zbok_H~{|0LC^Uv%)sJud)m$n?cmlvksIDMd2J1=2Stp>yS zhNml39bb#Bo?)4oercu&`_>pHjpkc7O>E?Z-6KnBH~`y!uh}Lsdfe*E3;F1eWIuK{ z7x2t%@DJhrXuE;RE7{M>LUHLTZocHD!#K)LgO8MLGL{XYnXX~L(e^=?m!3z{6r|}J z7AsN;QT!_e#fS-8>9!8vl~wbC#0!gA&F%a|y?9bzVn9)%j@WRfK#|5iqjFn*k)Scq zJfg$IRY#tGZgd%6Rn81QYe>NAbP8ECEZI{SidhU1iRbu1BHPGcw9-;KV8*7`<){;h z;^-|8-x()UeJi{3e=9NgC*0)#iXQ8_GLrW~$ykFqw^H^D8t=eRV0 zt5LwGTLHh=Pk+y++see~mX9s*xZrCOJ}0MWBi|c;{zTElzBhEbZYat^7R+FQm$Ga5 z6hQv>AYO=a>{&bj@OiE)EZ8Gg(s*u)7r*j7jCgQIwsF6JA^Q%n~1ckj;Z+w9x%?nH{Dyaa5K zcl$ECJG(pc+u0ePbeV@tTNn@`BW}leeb>GK6Tlo!G!Kp0+ccF5m7jQ z^m_lnLE*{}I$3$EKU>n5P|*aA63!|L>WX0T<$L+%89$dd1W+<1}_kn9o=@1(`ABC2}@~|HO33! z;9)DDv=s&7R<&)IKE`+o=#Fc-HZg>MV5uQb2dXjOE_l!eO!RGX>Z)11QLeLpFnrD} zO`JlN)BOcv{p`=5+3Zgie)r>W`&uC~dEJGf9^P;@X zmOu6MRvr1b$<-WJ1~mXU8sE}xe(q+oz3_$vZuR_K!J|}r23Do{kz+cFL;q-zsI%B1 zfGDxPU_gpALd>zQ)Hf^(9(l?qnHvp~g?N(i_K?)!26;w?^983xa~)G$y2bRb`7#@} zc~Ex27*4`IP3T~MbSA&lMa(I3)C9v+`88d%$SNo*MW>pQU`4;CMOGnUKOl4iY->z~ z)fGmeRjAltYAlGe0bFfdw;e-`qr16^%{!M;n{; z5762KdG4A!0)b*>dPi)7ap#SIZ%jfqFq)to05dhv6q^pCk`(y^^(jO|YJjyN13LPP~$Mk<49hRl!U;4+~ z@4g5S39b`2;9La93CqQM_Ym;Vy!=)}ZC|z&6>VIy^vaNaadST<%R>_^u_v?9NFDA7hvUKMurhFO#OMjSeEBu3G8^TbTCs&jCrx!&XiJ=S`Z@7pXH`#U6M_Or*9gj!$ft)hjAK`--c z!;{F(1L35WEmPStMED$ZNiGzzVPb@UpU$|y@ld8yQ+P~bJZaJR%YBK8x2^tD16 zLypV*eU}s9t=<>^S4svfcX_v?10vSz-atTq>!R^kE{!+gIyn$S>z?EDAT4VcHU`m& z?gSsIJHe=gMI~$-2MjaTv>UJ>*uL(is@iAsl{FVTAFOV6SZ5Hzna3O8sO0VRlPx6q zq*cX!Tawz{YcEyNb8JpPW>cr_j_S`jbPTztP8>~|gBpX~kzu7ng;-LlUG;$_tEWBG z(s>jmC7PkBLU4LEsWY}6rEx`-i=%)Rd9TB-)Vop{xq?cl)U*522mNsck+xq^x4N_0 zoKX9fb{J6A3}w)AxIWH@L!z_}r{jE*PKwTeE&UkB3yta~jCDr!R1T#;maA8|-d~Wc6qkiL&;jJolPW{vn=;YofNK@VDp-_loN(bUurRuY0 z^K`}g&%S1XEuKv%p%N>RG_G$P47}Ms$qE&kr!sG>g|fszBMG%wm7vx1pn48sWab7( z!vbG__`oIxfF((XKMBM?Gv!=Ln^A;(NJ=?p^Z2&NhCl%KUemTrHF>>%YlEp#f>pqk z{u$SnNShT6YfH46pbCB5GYYLMl983A$p$5}>lTp$D@M6d>sKSWgy%MUv{vxAFHR<{ zYIy&bWxlA3%U+4hkazpYEUj&qw)cAju7>R3AE=poHN$I*O9HqcNmN0W(NHPPw-ZAN zN_M8W8P;w3nNaH?htQ##DJ3mEJ&_qH%csf%@i{TF2mlyA3QR+)3=;|?}Obcrv>d%#8yRr zE-lZUPQQu70?j>P9ng`NTGku~94Ei|dT4Sx7JK|}UI(6}7hCgbA;r|3?c|T2^tiic zEWmp}5V1t@IUM8pFAlz|Dkq~?#*n03jAtccGSAv}VlqGJmC3}c%rycrFL#$BaoLT` zt8|tZ=}0>FL^{)d(IR`$vMRdVgXStaL~-9F{ci8XP3(+ixkSX1ZQU}!tW5~KnX_9wIiKA$kc;;XD7T zzx9&-aX;zwy1jnVE7>*6n=HV-JJ=5=UVOLd{bmo)1@KHLSe)=UHXCZ-Z zI=UTCWC9s(0IHH_!tHZJwvz03JVsVIw`L6r4RIuYLReRC3k;8f_xSS(hAYeFFk+w5 z)uPKjdJgC|EXFA{a`v=`O?GlK&*63i-hHqYg3578A5~&c%@64 z+r_X%hOB$zNV>j}1?)4u`j{|&PLshXoqR-pyT~Nv%fTSK&Ln#Trl;WXY&Wn~hy|Jq1_eM^3jj9xq=X1Ze|* z@R#pgB$$^$1|7C~*91a_%Ssk^fbRkB+nMS|xh@`WnyLD&USwc=;C zqlbf!!*=giD&UpYDSS$WqdzbKrKqDmKouq0gCK65@_@-|=?*~WMpXwOp|uB+`-a3# zDmP;9KDr$>ml=6)`&K9ZrbJ@A9n}2ehI_phOL=&03Q-JSMH29@E>yJCOV<#7V3r= zg_(5g;~v^sOc)uZvqfg|lv_Ms7C=Q04X*T9*LZ;aU9&Epu%~yBMG*3S#^G;&-|nvZ ztt`~5U1*>PcQ7h&xFKF2T81O_B&=H zk((xsY$iNQPI_9qo#z zC-YHzkuz>vKrfqP@lR`PUaEWuv@dYrxp8em?MHRCmGyfGsmW{er+uQZ3+WB)?3jki zrUt&t7ksf1Txe1*qgaM)RK3tG?^%|M^I<1xb1v%whhP|9{57}2oAUvGKu}4zQ{E-a(8tges3|Xzgi{j+o2<;f1r}N<%(4FCokD1$Pn48423)CY>)b8{QPG zwUT(S&f7I!=^U)eU&S&=63eGLozcl8C0fTqnK~)1>6(9hs4a)N$O+X=%0*@&EGGj9 ztT)3Y#LmoePLh-@H{)4OGev!&BG72Wo$h@BUR`@k zG3(UxrCrkxAZrE+=9Rx?E6I>Y`xAWl9(_>*Fx2iEo~T!L=@3^hom(*j)ljO?ftP|+ zkJ0A%x!Vf0U|<`Bkt{f~X5W55_7z5gM;0gOWIwa1A}lla80imxZ;d(f;)-c)1_Pq* z=F!>MF6k4`*kdYbcpFSVFtPbmQoZp1IF%NcJ&6A7mp74uKV+ls^b;eVwMi=X|M?Fq zty`qmz+haZK+~@E(yglhZbGtdh7|s>{>ox=+mcy5Iaz@Ljkksi5x3YY5v3N25Bn|g z8zrgy{+!DWOpqjhC06{k@~_tA4~`CPlrjhA4JlPd*o67VY=~RZjlBObG$j&~?$WcV zl8a~NKHEAb&-fk+m(OgEf4EAz?*4Fz^W)ptf3Qbz+1epr^$i~?ubUIYeF=KJK94egJ!L2%fm|EMZ;fXM4$hLDuUhLZI zowC4^Q5Htqoj>*T_<8Df_LdvHX=9)iyXPT3S()x zBWnR$e(^GYAVHZq>q&wS`H*9m7HUeVYiy`R#BWvD{u+w;=rA~Pjt`-(@&}m5n$R0z{G!_Y!Yz-(Bs#1o&y)?iWTNSa^tfL#8F0}793bRSz%7VZWjA>>%Ww*eSTWf=8@3vM0bf)e?-;mO8SF(Q_Kt~q-6!a! z*&zT;Ev&SPO+4u>7J^Szh^#*NhLYB3dOPQH%-ppif<_TEJ($(tvr6ch4<$1oH8RBz zQz_D`kNkF)#YoR6`u5yv)JaoZ&~^M8Y-EJ=BBU1~J^nc?NYC%LwHm7O_{@*0l%XdG zk!Z1hqBf-Hcu$MKqD%f>0gJH41a>mta-+aXj< z7L-K_B+lJi8=`XSU^uuW@cPB;XNhzN33AtTp3YamKy1vWpQvIu$cj%Ox``3OEl%}U zkPl>JQZOV}fG^xdUgKCq?4<*#P8jb|5XT~aEyUSZn`f`)3Ciz&i$cQlyV@;ia}lte zRihtj_a-ifMjPH`j#%mjuvCS)G;&83aeH(RiB$wukzd4B`>lX5r%r-=-#a= zz!K+Wp@#+NMsPiX+7&=;aJG?-$}#OZra5Rx`>kxko*>#M4*}8Ej)e4YCj#>w>?&7( zHVj@J*tQWwd7YaA+3Yd7b-$;mb-6Pp)hVT(n)bnBv((cQr$yA^N8X|I9JnN_#F2HJ_<T0+ zriD*pZE)MFzk+eG<6Ab+tx76i=U{fjokyLGGET9i7NbucAD$6)JrLA&=WZ5%zi@DI zZ-8D8LeQjFtYWC<=~Pf-r0zzPF2NeBnxsS|n9$-!g4L#mLEnR5u-rH>)xqMy=cE;1 zAS8SV+2_}T_DTQrI5{~zgJa!e_3(`S?mv0rKK#>D^I5NVoIE*$e@D_)`4` z%A8Lul-Z9x<3Qicvjupy@;GpR-9I<25jj3AzP72g3t(_e+w za^62Gu?XH!kRV^YPf?GVSC|{$-KtlzLcpAPok{frhS^0E@AL2?BKr~9Ut71S>NX+3 ztL2)-#zTffJ7kFXIibaWkDp(pg9`9@!BZVJdVZlBv03o^^sK3ekTW_!UmSRT2VC4< zsLI`oRBl*k2Ave#m}7B7z(dWb#>yAiJ2c2`cXtLGG1WWY_eS@-kd>B|jn; z(^+&PqHBrZ@%mySc)VABf%n^hdT*V05T6{L(CFGRi*k0m1TQUr-<3Iad4fJ&N*uP7 zB4_KJe@NK-RXPix-t5gz{L|KQFR^_vxzzWBC-5z)*YMK?I6GML5WY# z4$w8j-Hl@p0FsM;cB|{{R!QrhDohOk!7O+V+kf)43J*H?vK6ihw1zyyxpP=(jxzVcV?Rytuag5RD>fpYLRJ< z;y@Vsv29zjBQKG!^2L?o7LqkWZB!?eeV6(%VQUag)8c{^%n9Sa+btdoO$3TIh8Xa(VXf)&8euuX@isMT|<*9wNcx4;S7Oxc3gB>?1_$K(saQjj~mO4Hr`$8L?V#gt`GT|IvhfJQDV>%OO%X z_24*v`OPO9Bet5q7x^xKdk^wAjB54?dM|4a@WD4pKYMKJz#dsx6sbX8=*MNjxR#^1 zJy}x~2p4MhS8;&Y?HWTnf`r})y}e*7VcQ6^qaj*Rl^&lmpjM%pvM4?knF>#eGQ;~Y zCh7Lhj0iyug4uTqwjlkL9}uww1#6J_)%tpW*!g zL9u)@n(mIYeZr^(uYJfzqpC6JNx%5Lfo=(ih7lEC6?;WY`e9&(KyV2g z5d2?IO9KQH000080KBlBOC$i*nl2;&08<8&zcw8QL%c>>L%c?l<2ERNz3pxv*(7KC zL06kYk&sP?6160iSatN@cYpyvfTCo|QF7f|H$4^z&J2LTU>tn%dQx*QrWMDoadVIB2c@9d}!ohA{P z7i4JsIZ-xSWOFhvgS(g%QJySHR{ATlEW$}jj$R0WLgK_>8NLkj zf&|m$JfiSNvn*bw#H4SmyZ2#!l#=%(EhK$Pe5l-5E+i)!S&Ai!dFwI))a_8Z9_rInylv zA&jn-8rca_t60ja=Eodq2TBx{2$0+0dfN%HwZII#sci-`AzEI zP&n2n%RK)5SU2kmtje^p* z#ez1++zQz7Y}e)`$f4^jmc_LwcMbn3Ec8`5x@V+q8`0kQzCot-FYx>`j}5gw9D~Yp zG7i)?Ys*I|qtvt*7uX(L|%{HgM_mkiv~-;{9GU$q|MxIlZ$JYUX`4YRO}t^?CBIyB2+kLIy? zIsB(%{xj}a)+MAzU77KnDrmW7PU%WF=)wuCzlo<{1TSeta`3(viG3gFGYfz zyrFA#`ODt;vLjNE%~YypTY%K}N*f+LtlZY!w6l}2i?kk4Ic#!NOY0DIsFv3`DXz0L zF6tgfoD^j^hb9UE?uubuxf`a)>NwuunZU4|GW&aSOJcLeEz4FAPWFT!>|4M^3;40$ zpuJXq!5#ME5PxiGGKMl$>AI8wP(8$p<$I=3&ze?G7!8D^Kv7gLt!i4UvaxNe;*bs`5}qR z>)_ON+0!+l;b(BdA7|kFCiCE~%oam$U}Knn3W-Tpmf4J^QZl7EYlZCZ!Qk9go_`Gb z;+a! z2stpYC;8GmP3zU-o8o|HV|793=~LbWC3zeAba;c3U~9VQ{6qXZ!K5kOLwR`nDK z%VA}hyhO`EO8ogprs|RN)*b$G1aII<;GXM>0ys4#ZFMy~aAANX>o;hpkOcND$O&4a zD(ZvP)j_Sb>{O>Dx1|YE)P+u;lhS~H`sHM58rNmH7#<%hFV@w#SeXS$345^aWYQtJ z=4ZW9br3UvFp2=S<1-z<{Oi{eJJXR#sMEt2%+)uqXtzZe~?Wei+M(E$jR|sy*KL! zVzjm>S5T9$%^Elxt2Mq>Qg$DIGlhbxD-*_JT_q@-=9){@IJFu`(Y>z%tiarZK*MTXulHww^ZPZ zGEe59(C&~HdKfHi(f}%%zoACxt>qNU2}>FVS8<5&c3rQO;&HUCS%u;&*qiO$oY-XX zNg#|r08k~JuB1C)E7P2u8lnUe>2kIjVocoNbW+Vx1Y!;?0ofhO8WhnrnZXb&J|xrw z5sy(=kRxx1v_{6)gE)kL|6wS6Jq^Q?K1-dtllb(U<#}h(#HFc8@51X%F?Du2>CqH~ zW7GZ=;Rfe}6Ph}S=|5xavx0h6LDR|PBqWSLKZ;48rlN>Uq5&s0A&kJ3#24WyOTm9p zL{nrCpHE_z7snxCdD9;KCkpCU1@)%`Z!$mv7w0ak{rM#7pRl%nT|~VzY|(T|CjALb zx&82BiiMw?^!u!E*S$DBXFNE&7z|F%ML{P+F;$^-fs-z0udkD7X|~8!dduuvcGo$# z?13||)@wUXz=s7pdbM{qo<{@EWeuE6&%GYwRXl~ZBG*pIdBiwA9ngP__h%#|EOqWi zapcide{gYj&N|e8^-fO*q@f$*v%cFqLtdT6CrA|fy3CP>rHw5I4Ad1dM@Yv;Dr2$qQ7qHi>WAki z&jW^wiynXwi1N(D@_A!|*Gzqiwm~suL8Dspc=W$+_af|0$H!NR4LXzRT@J;S-}6)J zPLSchBX`_?X-ImF$)1{A(`wJj2TIKNng@EmvQ*>WJn!SWOcX5W+UQU7QdS6bJJA8`ty>Pb7Y6V{Ij^kD*hnC$MuMs*%f_%)A+v9U z4_+9shqYQ;fskGrivTq4*wn?dA+SM>ED8_qZ=!%N0+Cw>-OdKF7*as7l*9%{a-dUV ziq4H`lBPz}GK&&&vK2pQna)7ksUquECBYYePAHMvMgpsf(U_3~zAGPA%7f?_7yD#J zMKfcTnSt*_Ov)h&( zCIR8w{fHvCf7?uKwI4AA_gBrtq~dN%PHK0{m$;823RR5f>oSbq?Poj(e@)xD)p&3g zHW`M3QLzSxb(_R1ZuTzlogU$OCH?L#_{ z%kw_1T5t!bL$;zXX%s^&K*-66zHVcG&PA&qqdAv7@~<`LP25n8>%&a%KCS|l7;3&A ztGRB$p@t?(6T9+u+2#g#*XruXPsaJlI6q$F{3?tSWn-jno3Zmx^eqYZ2DXpN4zp<& zXydY|?@C{e4an@#zB>Q<~i;_m@IuR^5@!ABuA1Hk*YG&n&Thv3?{gvKFAaCdiid5{3Xg9L(0aCd?QcXx+0?$B7U zz~ue@`e)`nnW}xTtM;n84|dgkuxhPqq+ye1k%a>g+UFeS0;2xVm@|N;>&7H6o(JE%Zh04By+7Inwze7T zU$b_0HGhGI6LASYeCI*@#feG$jrLIM*>)YJdUX|V$1dJH5W=xL;<|h!(7zJU>S({y zXVpiU4dlI}1NJ7&J(1>D-7I+RQNwpOKZRH*8PkoI_9cuQV0$sz*m*Z}fDgvNOVgBPCNsCxPlh*~THF^GWaXo?Uadc*4?(7+FW>3oz;U6YYC3Iq3kO>(X=DS(B zn>oJ3^Bk~=dLiSz)!SI&4n4wIJnG0ZsLDT+ngVbR9u9P_0t$^JOY|ODN&D$f8Pbv^=0Op%TErerbXbc?oWd`N0yznWX8KSlZn?o1xo$Kpl%eOHN%h)=KQz&yIFvP0tJW*R*k&^Cp-RSGsagbj&!| z>uGREH==^|a_H+w!m1~eS0Mo^ygPRD$ds_4bK2HSrHh=fdeOSnE zoXbYC$sZ@IulSd8KTVXX_`ip6MxeZ*cPu7YDxQ(>G`%)~u$3`2vKr1f+&Se!CFC8; z!OB&tAGCjjZJ+ZB5R@5Ppmi!jVcrK~G#3V#deMIwD^=^sj@HD1j_Svw8jxW5rmKDi zP@Y@44fOoH+2Tm4oQ&p;Ox7?(9ZjUkik=(+FN08U%W7z??dj|I&S_XOJa2Lj%(rbV zEl+LT$rDAvS#bg*G|QK0ZumSpWh*cZ*vNQ>%P`h_$+^aB?2b33Y)$QHL^&x~^nZk3 zwtk0oKxNa?tR)6_$`ebP59%FMq>BQOQ55hZ5PhS^8Sj0-SamYm^*Zb7~w zUmeuzPe?~Ix6;ww7h1!wM{Tie3dbo*RyqQ*pCJfS3v}9>eP2;;PA6M%1$uA*^Dy1M zs?^3iviF5q3+^{0Bt(}K-)}4GP8yR#$nx>bY z%IxN6mD*Cq6!h)LH;H|6H*Sgm|7;J_>BE1fauUsYYL@sV{XT;#0c|+lWKyRz^{Tv4 zqIh|JY&=clFCn4C;u>tr!kQ%+?2nFAzrKlG4oYQ<#Cu2i!i^mqE(+hM;f8o5JzHjg zytHmPnpBu4N0%Ia%)FlrMq@@@<$v<^W*#|q%3e-V#J!ZJ;yKAXTijs;;;#(4daH#%SGbO9?@S?DBi{ZO6Iq`aHqtCkw_r&_3yUHUJJBjI9OCcV2WW!7h?e-Y~#kQ?5zol_Cf;>w*p z8&iG=()`_@BrWb9ui`U~_Q^w5;N!HQG&+5}77@+>(G+SBBw>$Q3Ste?EjWo2PFZg= zVtB%UUw6BcRB+Lrl4voP|zQ9LZFPYSQmP(e)CFG5$UOuFe$20Vtf=O=C}Iw zMNAvRRIo9>qp|87K!0a*q1e>L`v6v3{Zf%t8EeI5S)Rw97ZH*77V*HqHHgn=M1@ID zKoemT-v24O6U_N*AUQv%({lBS0DK~~aoll?IZwg}5LOBmFB*fYM=G2AF22n7cv5R2 z7lbCY|LZYDox?f)NlQjoHE0O^!JW^gLX1|_+N3Pp80>-$bjEc)lIp{h3k&agpBEes z(NzUcs^trJm8>MH@4t~aFuSwV-J&2~B#MnrHE(M>W=)?cSSGPs(TQmE>*Lt4Z-xZ- znSYtVu3{IB#*zv~RBQ7x4!iBG>>L!N>C^z=8}hmRJ?(@7iL zijT$qS|TkTyHcbAtrFRu;HuTLUy04#SDm!rEF}%91lQWtirCOQRtESey{8F7VfYJY zR6Blth|G+-w{V4Ln;E7my-u|2vxdQ*c0lo+)m+EmOjD%@xU@<1F`4>|5A&bQh-Yl(BBi!g>RN)|I~Ek`mNt*S#8%YvGh)zP%?XkrPqYEM2> zx|QH{p_j=VajGL^EyFV)F%}u(GTc*fcp20`=6NnEl?LrlT#hr^HX~oU z+bt4+i$I&QddmBhB9zl)FUw%hgJ``rs<^c1Z9QF;QQxd@rOp(m*>GI9r^rC%yjEOsmbAZ`KI3yWf2dCY7DOQ`JaPX{E>H~1_`JsfH9Mdl406Q2 zubb4w{A<}Ub14v z{jzN_7_@N^UVZHrwk)T84rcaNn7)4QYzu@EE;w%yf;wDZt5dMz4hVBuuqi#7;sBUU zCge)axW)2M`iG+cm&GNoezWRj)&T=kbMqF@zCZ7CVpo#6o@fS72@U0ib`zh6oF{T7 zq1hsr;#t7T|j*0aqjgusl7FoStCs5VSY!hV{VO?lv7^!rXsguz+!-r-g`$tk|3EeYw{WhMeNB^!^xIXs^|ge{#p0>_rpe(!iA1d5yEvQrEmefXjTc z!M?F+z#A6yu8&(I&~Rb@6MrvfbWQzReb%zsfL*FJb5$e+NBsNSg0mcTWuRiOSzWRy zXo^U2WvaX9Y>lfTxP2x_YfC3e7Nw$*YjYXx`M$u(;@J92yR`EHt#i&RfqzSLb*}Z< z&4?8sm?>&a{P=<7OxCV(rR+W}*R1t+s=c!^bA!W&RH~+%h{w>m)@t>E4gTu3cmgHQ zkfHsYS*}+&i(91Xkyx!-rFH2*4WSe4dB%asr&+_Vhco0+xnoQ2U zzSuiBe46$kmJLYb;9Q2G2U($fm?nN#la@?NZyfZb;~ME#?ccSP2jm2qHktmZyKsM& z9w^5q;N3ztX6dh?055P1iY-Do3-Xx_x=yw{2$lP_ChD|~NgOmFOu4E!1I|@!XZTgU z9m#t0+6B#(^})`SO=@ptBDE!LPo3v%A>H=-xwKBd=0ky4rBBHMG?Ea1_|OhkdBvC> z$Da&_XcEWm9Q~h;fv}RvPWUIcQjVCFxEk%Z@;5O2l6msso;fYeH%P{2eB|{9VKq(7 z(muw&1Mh0SQ;gf4ebSv*=9uFw$-N914n47sZQ>)6q&!I~xeTPIY>@(?4|kHbMwe<- zh8cV;!&%3Ts@0q&Ubl6Q(U8#nqTVRWOtjK|oHN?_pd%8 zNK`V!M(YYZ1HedHMQIiimw ztn)<%eGD!I$m#PXQk&Rv&~89-mj8H01+L;qX0v}ZCjyaZI@Ij1rE~UY z(x(mwSgFi5GR>*kHJvRMAVX!dSxfp}@QcUnJIpzEN z0M}tZ)}tG~9vp;E?TNP%b?JB;bc$1snDIY;7jcj5$LCzRSdD|GfiF9aeR6V1 zQhx2r0L1*U679CHd*<+m5~ygUa;~lYcz&JUc3PznqmYN{5Xh1!?-lwhx+xGvwIiZ5xMg z1czN1H|tkzbY~5>WzPm|`i%#|K`E-yxMD)!VPH81nu|9%oVi^C%*ka94|%IH-HXtO zx%W!DyHI14%XAy&JG}B`7kk0~WWgs@M*Y|hw{D3XL^<9~=9Wh}epyYOakeXF#~WL~ z{&e_0*UvT%Q{^;iQ5;#!C70S3MPF;0eP@IDfQFJa^OzN^tScXd<;#O8OkWIN19?hm z0C?pYLN}IrNCR*AN0)OMJtZMu>P&3o_Jr#xL*AhWfyPOoHX=DOa#*UI%~47V|s{XD=y zjRfe^vE&sb+lahMvfV$(%g0ddj-3D9l)U1X|ILuutR%k9UT$pHyCwA%cUwkfjO~@dii><3Ni-)z@O@e1Z-Eo2_ z86}yYgl@#H>bW?e5{rtX7x@$eOygY{MS3KP{EFxlES*>|nlQl?$KZ;O-MDm)rBO}p zA$2L#q(c3{1+z2NdZE~@D`A~|3J)|we1jUIcz%ViavHbPgfDQ>2tKlOPpbE;uVK#U zC&ZQ%JsJePKwy1prx$;+o_c#pX`Oc){y2cc%Ua1fd-l02V7j?aV+@?YyIGN}t}E)0 z0P0i9n%61ly~U#?cUIu@g+A`#X-(I1XQMaPX;{-2)g^Aj8E(4PpfCeLpQ45G)Wk8@ zNKuyxxSkiM`@hC;)Lp{aji2)sJyP|-CQ?2!tHHA$E5L5z{4y(>b?`9svORd-tx5Lm zr_&j(^zGXAOxW84+txJYVG`m&^T}L2gYB&3I=5n6=SBpUg~slImqn z`Q`Ep)4;yesHw2ma~a0T069yHmSCs?9|yX$DcjRV2d&*%FI}k|K8P`;v3i!e$#E8S zhjOrM&^`+xv!dRaFa&Yd0#@G?qE!K|Z+-C{>EH2rQFo^Hq0$gq&-_99`)=Nh(1(ny zzm}ft7gq&W7MBR1Tgj5VR}azLi!H9yo{7k3j6532@Ky$SZ*sNT63zxpY8D|bz&7mW z+tT2V8;vuY39Ji@Px4W}@tg%dBHo!OO=jEE8#siw@P)wLkt8TSv3S0qm0zDD;!{WO z#x$!Zj?cVn)hk8LL!)N}py;1!Ro0j&U6gm3W+>|{*eU>c_jpRnqhslk@AX(4Ko7334NL@KBBF^T9 zRBZ+?BSJx%fSLVfk9(b%EZSR&Ei>!IL6WJQ6lAzm{IRj#Bo|pTd==h1+K=MxjWBBf z$_uy?wtoqn66*IV3g>q9(NWt|^$e2bHI*{+Xc57g{w_7^- zul4Q>MqMPe4gZodSR0`jgPiBr`m#PtOW!WG-I67Vhj&pB0nq#}KzI*FSqm}&7Lv!k zUmup=NvT2>;qI4XZ#-pNHW7Lk6!viTybzW9LO$SM?{hi?RgxeWWe}B@sBZ2tp~jmW zJSt3R4XtvpcW(iY$6Kv^6a&vFJdr0)Da_TE3QiaI`HQ`BO3e@{$pMGDaC|5v5B47F zK6@W{ha0fcE@VSUn$faY_c$8Q(xX8!Ns-bgzhsw=rXM5YD|Zb5{?mtbiJ0yMdkHh( z5?CO#Fl{|w(wms`J83jj*>3BYb6U!@woVt``c=rER2Z~q^}2m78Ol{pNC#c9k$~!= zRF#oity~l6i-@S^*Gp)mVzkVEnT^~wV$5M#=iVKH%nwmxpByHHYs`9U!;4?4rQSgx zSF>)edB});e0cxNc8fRJ8i8wpV)a-Oz$aR1g`W~}Pr;N5v zrzBw;%J*lxwcI+IzmKzI<5(H$5{6?sxeJZ;ym}E#7duy{+((m5+p_ ztr_Ly1^UNhUtoo?__SWAKI97dh_d#l-97$63P{W7&G1Qpa@Mq%`(MCv$mT7Bfvhp% z*+JLy&X&I@-omh;08*V18Ciw^Wch z?-7K|ZkBP2iB&sc%tNBrvHsSN*&Jcy*!1LK&$&4rb?riF4H4~|ou;!^usJ2hk-pZd z$QGYX4gPgMJH}mHraQkgEUi;b;2U;X;9>N?q<`z0MiGGM>D}Uc{aF3svC#7&?d+fT z9`4Yr;ZIj|me|<5->TK-u2+=qWXuzY$>+*0vSH16PrJs({GR2^4_hsjC&g(jS^XpP zj*TUGpd6^2F~l~JqUc)dEZY>EQPKzfnNarq@!2tW@wj>@Z1eQ3&v$DqVBx{>xp^k| z$Mzt^#d5r%u`w3ZblQh@I$)O3Vo|Z{OwOv~?{`FB0q@*r-1NrW>k=XMeht>RMYco! zd7y&3th|S`3%a^A@U5^a@YL6j4z_Hu?One4poy@>RMGe3v2Yq~V0c}6g85Ef&DT>h zW$=_5?sgxMe>`bjLrm)sfU1^I&pVV=_^M^clsa^Li(9haS?~+zw|tE7GRj`nHPjs3 zs)T8CwQ|3ryD4m;6*Jm4m!; zF$-mAp2a+2c=gLxxaP4L_p{p#3Q?`ta{#t)0JW58hb#ic;UJAA{u@^%8u?wR9*!Oq zWfy6pX@hvD2;P6y`g|f)&6dE1OMN#mRkEt^hXS^##5-CVM=dH#HvFyv>#)Iwpq&-& zXqJ-XFl0K-yh>C6EoBc*Suhsg!AhLjE~|qk#4+)Dg0FRtw5`Fb10^=pBII0&^wuKn zoIA|cPI!+;_d7S^jj70nt-e>pW~@O9wwnyIp9n1;_WZv;REx6-Un&2pt2tbWqj8(i z1^?eMq&ae1<|8CHxFTFQxVQfw6Bzj4P+)WS|0fl=Q`^ybjSuG~#~1?cp0!BaPP5ps zKpH;AK8kQ)v@$o22+WgO-Ij$9?F)NX{@T8PKDxRy5NY`IFe(d&8p%zBzE&E;8N8;e!G4l#`Jeb^EI!UUkzj zF1`Z1ljrq+;7Xe_)EyB>IAbY9TQAx_SWLN#=YtpX5PcjSZ>H4%HzeJn{5guXtbQ;X z`SO;+wr!E(7}hQJE5W^3@XXh{a$>iNK-2e4z7`*Po#FJ%;o(oP$Ir5<`F?gt+3&(G zJu$zrr;Cu$uVDkur`tYc{5QhV344f;6Qt5#g6&MXRsrx__NnGTxa_1%~Ex7WJFlUXdF{ zP%Eh}eqnwi{{~GmesYEpvrOUB?c)%|AM*A-hN;-wXmi^bd0)%(Z^j~p6{5(oI3=Wq zQ7QSW9{Dx^4*%ZjVnqY|1pbMn?*gMV7ed z+w76CrY}m8Fu+_7vVIpv#7%*U+Y$(BrS)&G;70-i%cEIy&E$J;Jht0f28zwV>I^;I!H0wqDHhNW;l$X zUxI-Z*!*E*DlNRXD@p~l4XdyX(Dw+)di6uiD&5cdDp-@Bc$mPfm;=r~1#+|H*p7!{Ncb`v+u)gZqb8 zhbs)R<4K-~Ao>5rg8vc!_eTD|#k<;Y|HW+opY@FVKhXC7j7rvvq(C4tO!kXZLU6E3 O9*G2_P>1|y)c*p&IGu3- delta 25413 zcmZ6SLy#^^(5%O{ZF|nxwr$(i8N6fL<{8_zZQHhezyEG7BD!+Z(c9{(CnHxwK!V~y z0E#l8U}!)u}gUd>_9*`tH3}AKzKmLZdSIYjHWE~W`7y1UDQ;efk5|s z__Y70+&p1{fWe-Dfq?$^`zt;hk4^EK-ACGz2|hiVR4eI*FU5;2xhwYT+@|rE#%-6l z)z&Z)2^9RurNm@9E&4OKCMx? z1*eTqa?L7VEKgQxYOn8`X){b|_uMzVEI$&7qg@54FJ0k5qS%jr^YxoA0KcMPX)ZfO z&0s2Pboe<#a`eKue9*r@IU(l2zF9+kSv6jiy4L+HFrI@BUt!~fh+#rcV zn4?0=ceXuPUURbgsN%*PF}&8IWQ_qB%CMK2&QrFgS`AbfPhHM8*W zp+6>@h~Nalx&uhl>^Y053EwkZdIEd8DsikbRPC_tF5gie(*C$Ze?NoKQDTo;Dr}V( z$Ey$hL%!W4T0RZgV4R+Y_#~d$I<{^~ZK@GC005{FfR17gEe28(0S+63obU6`BRD$R zWK2#aRXz|H%yb}6AO7Tyv(sl(@)*4)0KqzPK{Y3+kvwYWnF6nt^aW2%YRDCKv|F4b za3{&$B};lR9SJdz${bg4>Rtl_BR{uO10jp_*s1C*R!cOtT7g`x;Fo4|thGKllx!S} zQ};;o-IpzcSN#a105YR zhVmNBuFH@sZ;b598_csgNV*t6RS*glVV>8^82!>T_JZYfh;}z3|685@l zYHanfVMXIv#JgR@$ngh-#hDyN`UiM}ub(&U*n^d)`a*#~g-PDN4C6TrI@-&2XnETlU>a2GwF?8 ztdQN&fkKGv*xTe2xb6q<1r%V=nq8=S4Ijw>6x0D5n`1PXN+ZBQhrhb8Q;j3k z`M8P7da+lOUrTDax5fv(QL4}6H0V)qiH<)8uw^WfncX?aIeDr z25hVdd{>)4g7fCh6V6sQR$o72cAfRQvrZ_w^_YBHH;BtmQvp5J+OwN)0Xz6wgnhAR z!C4CXm-{inG$h~I;`TfmC0OB!W>n{E!ZE%z_c^^&858bxEe87wvYs0z(LYD0Y+$7V zJdXsoBSsc$4PSDmS${GwdURK1q^oE%$o<(JY8KT}Ad272BpaNB7oG4vsP~^ls-&gR zEQa7wFFs%sK~n5f=f_z|wW7JUJ#mGd954n@q!zGvbMe9xt06q3!fzq1PQ+!OCZg>t zix;u>gI2~<+@jX52~GyVlS^`j1;AYZ7dqtA)-93U?i}Y!n^4R9+BswIiO?#Vwiq<6 zF#rM44TTVHy})m0p`AZ@(BBWD$SsPJJ|M)PH{w@!dbx*y`Yh9< zlq}`;Ws%bxQyk@&>|wv`+rc#g8{O^JcvJ+6&-2^b2;BFhwHSu&WKXB$c0;a1ygY`QT6il|;HPPt;Q^J7Vmb>z?nQcBmenStmoGah=NJI>h9e#_Ll?bOLWG15ff z3cM|~J&QKM8!mYSciHtqS5bGs^Fl;cbYNNjs}}Edk~^bC!WLTA& z7x}^AJLKLOKIid4UO}4f>q3D-z6UwrG}m{)SQ4HU;3ooMlpGui?mWk1K~sUY8f zhbt+J4z)o2=tbv{3bG5WNj9}`f;I*bZTxP|=J{xFs3pCtzwmFKB&1L)H4QH;;I*Du zyqJ|QJg}-xU^K=bjY@;{q$(e0y$r-KeHeEU*KT`?oWYg(7K%IV^>CutZD<({YQK>k zGR)!K@K|KgTvaP@qGTG75b>G^Z@A0EN8Ki>>o(B?&WL`u$tTf&!zn**ur~xP-DKf< z4P!VpUrTlkT45FNr=-nnph;@*f?Q9$kWAnSldN*hg<5OW76&OEvYqv4s*Z(wwhCfX zVwCS5!jZZ_q>_22!3tm@QHf;ZsB}yLOIu8QjG>HMO4DRD4@w8HqBKcX-XsBaya+f1 zuR+JL1q$!P9|Gwau>8WpV|AUOlGQ#gWQ+1=wgj@Vd*Ck6G$q3>R%b_8D7ls|C9GKWA2l)2@V8(mcN~(Y9>Mifab1+*12bL=Bg!%1pZOio-%rt9?EX&W)33e;sm@Z*@Z$;KXg1MPEMl{Ih zM;3;DaSxS^%{J4Vy|L3~zdTAjWPJ8h%S;eF%4UUul)4#IOFrVlJf*KnNON_OI-ika*N`Z_)qCqRWLlT_C4c)8C28NDhr{b9}D z2-HkqjDG)rZc>q#Day2Vjjh$O+6CGGS~87f;o|5;6pOZPE1|?8M3^g^)QISMf8dEB zD7$DwkC1@P&d!Vq<-q*!_drgP^nhp}2TTNB%#A|1!uj(pJ|!0AEkIw^=DLB4c!-Ux z=&DQ+<)-MsVzV4sA>&OLfb}d@OB$ZQ=?ZjeVt72^ufhjn=9^( z!fl#_RxfQPhcOFhI}`;Xf9+la>*<1?t&W4Q$ec1Gds;;F6`S&m{L9M)@KnhGr7Af; zCsD(g%V<2MPZC@vTFP@w!~RFBJ$ygv98cun_Mtu)w8 zX}_pb(nke($r|woqnQ);Z9{c}cz#r%UPzU-Obj9c{`zQMWNgbKRXJP#1pEF z{Yp`r<{FaT8pirBE5NSoV?j7hW+#l7Vh#| z>23!J3@Fk1>hnB=o|ITUMfAz<(P;r3GJ@oq=Mmq%tZ=~O#J2&6q62Q-B;o}w7XR*> zPCM@yJGd0WYC1Oo$8p{>eY-q+#n-+Ldv&8ir*+%dk)z+;LScL#OStY~g?6|3t&;>E zzF(yQrP-6Rm#XQZ8@YYqXXu(8b|fg`vUU(vbwLpq_@t{%0X_BTux>TSU-N4fSYpod z7@TlO7;dt-2oRkN$I)kh`L7!!W{W^;DZT#WXCsST5O#P2zL5QR`DQ2z67ncp zltp9o9BhY`V)AfM*>-GDxW+q~8CIE_c&?fu`TGA2f`(mg_V-=hIEU~)aeoTKhKX>W zA*>ZGx*>)E0;fAWw;3!N5AkmB9gnvKuAbcyqv5u%v%<9oH>z6#^y&gOUx2@QEnaD) z4jFwMWHOG_*zZKyS+xYsZ|`mb0n@s5d)Q#`X~_om!aun)eIAiAbGw3t|br|?_&!7JsJMU6OP7Qa*;lb zejzOXR`&}8tDFx!&+e8%bTcMiOOLmqhp>EB5 zu+^_UI!O$+1$l$)j&)4gi0(zHYVjLcmo*#3*3nY_Sb_@?= z6BvRP3_{qkiaP`s;NaPHgXq{`eV{273{5Gq0HJwmzg?5-?C-or-!pr}Sr2jGGXt*m z*np@rGJ&kGfUArJ1ulkr*OTld`bQZ+=S#7Qk5&Q$4~7#-S<=@J>iYl(eL|cNTB(q_ z>t97Yp0g0Yr0YC7NrQX12ra>r2Lsy`Z#Y?vPAYNhvQdQ zb`--QtA?H5Dm-M%7p|P`w=ReFLhqa`21lPQdkU%Ap)3l&)O(}q!3TewurukH{Wo>g zNmee*g#Dg=fC@8C>mND6NB`!~4CFhl|Bg|ewXBbfx!7I2#W40Fd)IU@=n_Tf`JX(<5BL^lXAmHT75X<2L;GJSivlPIGE)@=`v{?L<;i+u62m_2o=&QQ z_lqpK*wIbE+U}^Sz^IC}uc{Nhz+-EFKW~bt!==TYsd{fECIe4d^9Aol1F5}j92wFs z&Iyj3URN8c7BFJ1An9|hsc-%0JSAVLbJy*ReDL-NGb$Oj-++(Ga>+I0}y$@E1CBX>KXRL9X+1o5fw+N4dpM4)-apaMGMNG~Z}rPcsr8jE#5X zTumdb%#MT(YxkO)rWPj|z&X?<1&jSs^(Ub9Z>G_0gGgEO@)GyQ1&TvJ#Y zS@?jG18kBv(jUO(fR3c+mw5tCc;x(Lpqlp%?Os;0FM+9gL@OPS7284iWq|iEcJ+vF%>$4k^^9XcjuNJNLvB)q?vVsd z>70c7?)>#tq2~ho6|uIgG<9mZ%-(V2K?)stu}PoJ|JC}hC+68yM)(i=m+~=GSj>OZ z;irn+DtJ-6H6$YF%_Dx}MuAJuD>!8(RVCJ=Bb<_f07(+e%e1Je^@z_Ta)uIr$eQ`% z+G!{=p*-NW;`G-awBp&b8>2||tHIKMlv5j)sFT$N`)hRXjG6Nqnwyn*3;)rmIHYz{;QE<_#`58U+4zT04O z>Y7PK*Yh`WUetRxnZ~u~A0ryZGEbTiIjD7!9Vh_9PGwpkCexGHV1;)FrxB$ZP~lsd zcD62cb$2254K3k068+U_Xql2F%B)4WsTVL&mWg#bC z-|-z!uN$*Wln#mt>h0%1jO$A$3Y$3(qy$itI>~$YH@eauO{4tT8+GcKO&8c(l6l+2 z^cjhyGKn+OEDKzX-@W_OKeFJRB@e^<4mj!LULDt`h*-&K3dFd;5nQwDrl|I{?89tP zaER&7Fv7RehD&_8j#&N=ZDxpBt(_QF5g#UKrf9O4Q)254`*k_3FzBY$D_k2^bQ<6p zD#S7u`ExEfUIVTFWV8|Wb`ZUo4KC3@9B2b*yWeW+=AR&{?QZ};E#&}H%gh1>03!z^ zY?#7mzX+E9FF7N84(DJE)Y7a&{2v+rqa$U2;j^>v7^rxc<)B7C?mB}iPKn$zYWVU! zdL<8X=qjEvQ=`vnKf1(&8c1a}I5=RT4R#tz)MoNW(B-(N{P`o44!GIKfJr)~W;IEd z?Z(8UT->(`E8i-)T*l<+ZPJLSwk`E}iinW|TXt;}atO`+mbF-laE3Emd15v~2hF}&WEbF|Mm55u zx(tP+CZoP$Z4`ejbo+WfLb}q?aTNu!lgW9zN~|VKoDB_RiP2trrtYCo)93dq<89e3 zrZd|5x;tlB^U|-#EW;~32Ss$TJE7s8RXijaA?6yGPYUlXb+`N+4FAr%inP!No7P~E zNA4j=+N+1i4mW4$xiF(pgVl(rpay%MkzH@| z72`#8BLa5yLQ=i!f(nr4ulpkSQ6?K53j0+FGc_82TN`CQG%|ftOEREg9Mupy8r5)z zo1>=^e3XYuhFWP^QxDqr z8C*oKh^-w~uQkm`_p>U2{#?PLh&j>Wkh47s)`O6-;sn~PU8i9FyHz_0`+gQ@Cg9$W zaaXmqM>y~~-vW3_)^Ltb3pqGZ-O?YI33E!$`LB0wQdi*&ICq2sOKd{w>Hj5=Q;9Dz zXwXbh4nNqPpc0o|lWui;tEtMwB-7WNvbH}RJs3{<$Oef-agz^a0gq&TNug;ywgATg zojkQoZ@U;tzLaR`#M2y8Zl##vc6r-%;I^^V((b>JUsMajS<4rb6Aw=Pg9PNB`5_a5M zcZIT$Dw7<~^V3bk`9sVBbpXyrCziPbsN`juujL+0&F*Itzapb_BX=uV>7_4IUEMxw zL82XyF#uk+;-Um;Frl)ke^Xk_oVGE_>#)Fm+Oe;rWbOstoM~m@w%_lF!-f|NYRd2z)*xc)lf||xeR75mB<}H~Gh5&CoM%k0mH5Jqm?ba|o0{w< z1JgnZJ6j!zW3C%>WUdvuWdIjOnRbx&&r#g$k6;3w$gok))X}9pyr_C>Zw16Z9E`vJT{Q15J}Swdc*)8x~C+S!a*c z3#M!xmbRVgK3nRWgt!>%)y+V;)tc7kOZs0g%lr;mF-)W^gNY*{1mR%D@Tn@5>UUA| zLjcQAWoF!)8YfsJ`0=@t(f#JvOu4M74aM`ca(y@7#DW&=Bc1>Zuz~wY@wMbBSx%0E zvwF}$pK_L|8y}dwQbyyLPsKu}cTQ9%>rEmA>c%c|esn}!_<+&bM=JtMTG-t92=4ZJ zWKVD?Q@dAfetr!UM(-CDz-!?47#=v84uC5K^!qAtU#k=z)j%=8sNO$&KB3u_t+lqe zM;`uBctj3GEuo0;J{LQ$EDpgKNGc5<@J&kSbuU~*4r}jv)`N0&_9E=T?-()9V;=2w zgaEE?Ws-o#_eJHsdscc_(1G8u;996J<7mKq9)9+PC>15Lka7O}6>`qMw}^6h4=}_o z%8|mm$787qC1@Kr4*lns7|<`niY>?d@x8G4Q? z8|ly7td1)XiidDzL$w%aKGh1F>jf_&?3Vb;_auuM?<4A}~I zm7iMNLG}ntb>nw~GS>Z%9%sA8+Ulv5Ci?pyBFDAI_WmsA2MIjI)e+H_DF<3Fe$Ob% zUVRq^M7Y|YjCUZfu{-X1ut$d~4Q*s@?5f}|W#A&4Y+K|FGvZ{phk)xJ0$$;r^7r5aJ|mf|85v;?TWVK9P>8eA`=@8x|=$8 zFq8(0Zl$ggQ&wqV;jW8>rn$Z&s+fAX)X>{g23g!6Ab1+6J0C#EuuzN+`j;I0>vFj7j8ll8Kuv*NBsP_TIHtl$5h1cOm1T z>5@^mY3A(o2tEVpkFL@SwJK-Tca+;@$4{R7T2PGR(I=3D+(*qEwG*3m@1^=X)Id(^uVFs;H}MwcoCc2CtgFn!#W}?Q-0DQ5Mi)`6!S^~D0aqsO zB!dZ(6Ev=hK%Oa^T;WVDn*6*Z|kWz9vJsQ5ZUqz>Q~fLh4_4ouMkiK;ELP$y-z zSKa!5Y{p5aCn*ZIavh$6a<5m7J{^X$&ai(wE32%;+648dC`PUW($X%F*JH2omuIH$9zQbLLl;D1hb1nP4`o$cmTWx;7G<;%Ha3HcCzT!d?4?JvWx3s}U-J zn=eEV=!{+x-~qXNHx3fJNJvx1t43c1lXKEM_you~Nbwy9oD)3my^~Cm4hd`tJ0fRO z{%51z5Ze@-e`%0ig|h??@Is3(@Hh(Czw%lzg8aEvHE9K*J_NYox^X`Mgd(~d@7 z>$jl6FDDio4h6B9j3mg8n7Fl*7KTEqbWMOPBpIqmulaA=fqbvm1G^4Phul(XvNRDS z*LlA3flivb=&oLMD*@?W^<`ph!WRFm>(MCW09lH`M=|5inMk0Z`Eoc6puuuu8fRur zYpp&4DlDFAremDCL>5dqylka%kBiqyHd4}`y29DMP`#*#WS1OCG*jYiv?i+#N>jCL zGM)>@t4@o!wv=Zb$h&fj*5|j!YP+hdqv~{IcN<>|&wfO+)tOgv>(fS;Oi88>n0b=+ zH#o;3O6nscy}5lxlFXg?10Wdw+shPZxN+wJ^S7&>1Kks<`z?OI(Vu6jkK z@r;*u+$htQ{^rx7^B7VUbt$dnUB#r<;I@`UU8*!CeVd*m!9)$djci6GCHZGA>~LW~ z45x|?nRE;1`kf(cx$B4Mdb7Tac1K?Lx+&*_u?8q*yHRZv#z@NB0xoywMm7|Dbh1mv zK3hCqiH6uAnqDL5WwPSzq#RchCFQ0}lR!pNm?uAzW*+L)=CzRFJM^igz4N9i%D^0} z$`D`hCkGx^$b8`$Un|w$S$Owl8hN-bjK}Oe5Q2KRwv%|SxD>C#S9lW0yq0-wMl4vxSy;?eXWpQPtz zy?a}V6xx;~5QYWYw|9b1TvK`FFtt1Tn(QLQvcqz(5~8?R*p_1Y$VVS`)OrDPQ>P7n#l^u z2H{OKC>=IoTiWBk4EeHt&Gf08BcYU7gq=wPR%(&OZL+9@v#Q$831gYy!Tl}WJ(-dK zTNWJj@Vei+2>E@ko6rK-Rch^MR%=xJOmW9jT!u57n{Z*qh7S|9!Y@ghXU#RQ=HL8a z_9d~Mil=2HNK@fT2FaM@Z)P}aP^yMq=`j2s*%O%qXQr`M{7o{_v@3@5XpR|2?&tb} zK_%-k3^T;}X2}uJRm0!*T0gC=U2BUlbQ(8Si5;TFj$baK18mI8w-ixM_|-+?uKf+7 z^6NspyKr8QyIQr*;(fMEr^Hvdse{)>i@}WU9@a%A2ZdM+RDkzea^U^bwfv8vJP!vX zhxZfA${LFg4Ms`!T(GN5)t4PFx5o2#e3iZqy9JtD)tEGKWI87(g5#4?t3dEob-(&q zaRuv)tNPZH{r5;?PM46q%~hxFJ1{-u`r5Xw9r6;y{=V(r`XVTgIr!` z=*-*S2Qxd2ak7{VEVoeHe0CY;2TiLvxV>WZ_S&y|^T@s!9 zwH+qAgdypo^ns5{fSY2&rz|+XUhDv&9RUZQ`I2_SR;VQ~cX+}J2}H=L(V3q^wz@7` z{qZH+D~M|Sh4{uO3tyzdMl(K&$x%#{ndwW=NR~yF=t`Bn*(aYQ`d5b?YV&1h-jB|l zpWWYP3}B%`Cb3`|dxC%*Gz@Ue8}5eo>2^H$OynK_J-CibcT^4lA5Xy(k1%u4G=}K+ zTEq2pBMIU$nSAHxta8?xLl(1Di$0xzNxzEDdeh8rr?dsq&c0;Vhs2Y(^(AH1lOMwV z-MLJb60c`gV4*Gvgxo%CMDVa4gz3TrV@sv-10amXe>Dc(Mox?`ypPdRTO?`4rcXto z2x*aF@LwK{4^_>;gVa0gbZ{ha&QAIv&5CRBrZT4_%y@Q55;Rps{#efGiG`KjT$XL# zJZ;70v`?jlC!=oI(}PZ}bRpQc67Qg$y_qP~sjY6$(xKc{cCwrIk;d9Rq}y1Y(hMFq z0OlMyDDXvXwMDLj*j4#0Mb3lVw_>z&+$|b$wc93_knLo8g3VhR_TZg}(^^K-a*ad{ zWD)G`41|Hp|7|iCC+3p>^EuXLM(h3K*23CC-PHI*Xv=$Y8lR8(XL$9-HH;DbderVl zUrIEM6v?v?Q=?X2T!V-=nDJ4u=}uI60zhHnBS9?UbtJoT3Po_HvDHxHv~u2t>1JTA zh*^7>BS->&m zpGjC&^4s97({Nj{@jTC%D(Rak95n6&$>6$L29_yl@Y*$rbn-tLgVDaxt5NKq^EQI7 z*d{p&2U-EJf9g7%U)|~__=>;;%qm)^r}^=P4lqmB1UN#B;$pCf_x^&lQ-coqZS>$F zc&)ERN3S8b?=ja#ul{Wj24d7QjsrNZ^Co5-6;f>8h-s`u6U(ELoZWH*}zC?aqPA*bB**az=X zvZO<4%owlXn$lNb-fck+^vEqvz4GzC`tB-(+3ddFZGj2|dXxJ_1#|V=>;T}2$-uf6 z*ru(8F#V3 ztU&M5@^s0<3WXqYTCTwi-AldOrq3BzRCyYty9Fi{$vf4Q$vmw{49YGDg7t{hX!RRV z`O4J#CjFIxYn$ZvRcP?O1E8t2u%Cf5$L0yZoU)Q3zp9dNfL!g>+yFo*INn6<4pLkH zoJnJEijLblrp6>TTY2s|xKIwAb(MALsIVVGE1^Q|$M30r!$)7u@Sub#JDVaU)f+vn z^Clv}-!}UqW-+kqjI*5Pu8ebM4sdURjnqPS)sqnR!v4{wQ5L~}g@OD=2%WX1boBoi zlGFQci9kj{lOB4uNCISYohwf_LCVew##6>KXcX%HbMT9uUZW8jpoDi6IKQrWwwK$E z&DL6I;Z^tL#erkLj!qT0h;{bnvaK-DQuOxKhV!*?#*@U?yf_UV63;Be<@_k*G6t2y zLGLiL<218V>^B5YtV)Xro#1-j)A>m;7=B0XKyiJWV8CuUNdXc^l#@r!^T}MGvtr5D z_NCNGUUg;Qmza0Km&L%nQzrPbJ=|`#T&?za6I_9k6L+Q2i~Po@ z8@zlA2D^qWRe;Tc5l+(IrR6dU#wiQ6YYUA5^*If?FKZPta5@?mJe}KD#6@+JsrFWM zHZF&jfLbPc-k*>u$$m>N_`qH_E}&Lb=9#*ZB=PYsa5EPSN7#f0FYvcQ-EW2&Xk|fu zwq7=h^)&SUvxnb?!v}*cYrP;W;6 z?6iBUM5vM7DuS2N{u7gPPX$BeQthg-b=uD zBeo4Rs=@;@4Aftz_ZlEhb7_pd&h$$I;m632cs!5L#2~%&lx%V4%?8TGDX3k$ftrzv zRFhX+1pskXEdPIANl7G;v}pWB?PdI3J=XxUKFjh7mZqvBUnD(`knbNlZ`omHhp=-* zI_jR}C91Cg87rr4ufd;7%!!0R#zZHc@3C+T-$bY}z|Wwyjm16u6x3?x+>c!&O#Vi1 z4n;vHF&mj}Z=L;o6PUMxl)4cR1{Jc!aJ4(pKY&6RKi<}`LwoVWr%a7@-CI$58+S5I z?~W=zCr^{ggn6b1nTCl8>RH&dzwxQ-`fi zvbpy~=-JO#P!XP3ZTrYhl&`yxdx`_>V{XW$l-B8QRbGPwSo``+!c=K6U@=sOwY>W1 z>Ht`qbr1~A&uu1ua{`QAh3CI?2#1QtOZJY_McFo;{9QtjqWuWsWR zA-{s6Q^bLiCV92JWa6)FOTele#Rckl-<=sGAduZPn$GNZETTmhw*{poM^>EhYG+Ng zEXBwtIIZ)3Yrzb?XpuOP(yJt_@2 zLVrR_0mWx5P59MnzLa%nBGgWlL?h+8)+i?qLFBqdIIKpn)k0M}I+oiRs$e*7JOaOr zgv?`5W*46%3|_z50vd-0k)KRt_W5uHiF3Of$DhXsXeNNJWb!`UhgJ)g!3Fa07!Vjk zYf|eCdHdFxN28hw7Giti~kgoec(jH zwbo%r2X9k2+JCU8PD`$lGj2B-(J`ucX|IBKH@~o<2>hx#Lc442jq+&Lt|mo|CRfx1b}j2L|P1G~y9O{fEE+(a-3Ta6db^ zfvw>x&5R)`hkTJkn#Cc?pW2Vv8(%>_zkijaJbf`ASvfd>d5AbiKHR%=xW*Lc4Z21Q zTK#)z6pOT}#Hp$_l~T}cHU%+h+R-av=-TzvwWASW%v6a*QdXg%L|S+pYF=b$)*my{*?zD)B*^%Ip&r8jUJrb~wkZaAkfl|{fm;ka zfYLRl?&PO)4hBhUnF%PD8rL(aA*yzC0{R|R;MP)u)H!6L740R)|DtbCB69yQ0#;XM z+$w!pYp(@okD~1XUI1Kajq+LmW3BSEQ8rcu@Y`v*Hw51Q>5li?)GXfI7OvxZ zP`@fRdL>Q;BM*FXdif@Du}$4c1sOHlhpgp&(X2GDXFovo3=>S$7n9O1t)X| z6H6PiW}twZGD*~Ux8iUgM>}KpDT{J@@$l@Z3G#J!^uj3adz+8Ph4AMt=e=ss^sbTM z>z0aS%${I|FY^z9C;nSLN#b-cB&^iS;bdZqXVnWYjHE&uYE52$RFcZKal%%gJ~EB# zxB;YecRWCMrJjO>5q_i7tHB?gpo{Kq@Pf_1#|5Yx^RW3eJ1@EmVQOycJch0nM|1-+lMYdYJ z$L0tZU?1djn}ym-jDLj$#9+csE9_!XYi!_7 z&nEy8^q?Jga7w<$=)VOIgmFL()48@++QMUB=a-47exrHkr+l9I$3?T|*=)Z3vu>_y z;o$}J&W^I?BDZRa+c_W-w>^*vV)s;xk8>~|k_#E$=S%}S?`_(j%-e;rfI!Ae&w+aV zIVNeps$gT>W&Gu6i|iT)7=h`0e8YXpy&KRl-5Ub?e$ChXja2ibi0C!Gx^@;|Sj0xv zW{(>wM6NhjB!rcMQAA1rmpDw4qw!A|W7RGJ#zxaw=iGPUya9%QE7X_1M@nP0iC^(~jhf57i(rM9U5hFy zT43yk?YZGy0Ic|Wrw}h;sxYLv;zf3fGg2T6q3|5(Y-bf-p@B(mQ0Zw<--K`qjACqLdW#J?5bBIv{pyVnMikx1% zMCFE~{Z5BN4EJzyA4YTR0eah=hEVV<>1EMM^o5o6q68ZL(bUHW!c*2!-%c|_=ZVcd zXL3!10kCpw4qTYnn0-rH{~Z9`5L$}x3>kFW_I26bW7mn{@FFfg24?KBj|Z#>;45ic zyU@rjtxT6+AqiPqyEzgWEkq8-ZTRX|Iu`lbc4Ln`BHnGqK6;FMj+2DsA`wfo!dfdY z%{$4)Msb9?ceq(1bffoQg(&fL8vLzWcWZ4A`;VfHLc z^u^gkaqY+>oyk#I+%(yi9h$e-`8RUPBWmbp2`IYc5`v_sCm(`Al+EUWHMe7-v~m#! zD_{nEasAf^Qein{$zN$=Dj!N}v=BDk^K)sE(^Jmeb0%!Ey=6FGD*?xxxb<>qvwVuL zG;2e0`3U;pONwjU_8xmpV)GW@N|;T)v>-|jEo$DfeLJAC8hWJtdCVwNVsi%!b+*q9 z39~>rB;k~WQnG>odk_r*Xy3IHq^eK1U(Ua`sMW}w_}$3IHM_mQ@Aw!R56Gd+RxkJc zt4jZY-pd67Ep63uA%H2?oZ1Lhe9;j8x-xP&mi7yW5*BWc@rKVLlxGdFVMd#1LKY+_ zrb1rzTTqNv=VGJLd;@glCbo_>;4sbLDk$1b@@x6cdhMfP_rrQSpK2kew3&ie-+O1J z%`zrxMXLH1JI&WD@&vK(^OZfLn$OdICm0q2*r#>g8W0C(3}BLLmB{tNzOC|TLU;9; zqF^+YsodGl0=;LN_SuJ6Pk@G59p>y7Sa^Xp!RUH%XC=UtW0M;R865WjBO8p4G(h)W zPJb92O1@lL^0s8v#dTcucSgwn`u%_NY!-DF9JmxfKrg7kKq&uro(=uKK%1SF{r?wg zODbWZ0_?o(G;wSm*$TW_yHKGtoS!w5rAWFx?Rr3f`V)zbCl|TqtT@Gj1qJ~#v7|$C z<{#7y`XS!zG_uN=FZRqk@t#iA4T|}($b60n3_wrD>W4*H!6Aj>PoGq>w)c#Gj=*a1 z{xQ&$z+dIzsSu7!3YTM9;rRKvFzdW*8`+4f2MADR%AGV)XPxxxO*K%-op9ZLYqX3F z?5E>*#{*?pqlS6C(a5r3L}M&M?Z;6xdNP|+?9|`vgq_c)cUv6F0;jL&{W)l@jrOoL zgX@8Dv@9~6Fur6kdnd^u@UpQSI8w9dW~V1~(kpEo{=!UG0?)w&TY`6kz6QbC`*iFw z1YpA-Pv9fTdl|TOV08DbigPR**?cEEB`cbQ&5&hhRX8y>t_4WdR@>$DWE}59)LWak z7N7FWU|nS@Xe;+Qi#I-nF?Gy~n^Fc>6Hyap5=M#hWQrQi)L5Kf^dd7^Mqu#6?MWNq zg1uYMm=_gE+aY0DUyT%RGVa#wS%MeW0Q_xVk#0^M-wijM2I!}S?OD6j^?IYIp9(#~ zQj9Xq`?h{&uJDH58bytuFULK4f}kfYtCJ)Mc=kFJ69)DCEX{|66Xj zAseIj-$I#E`x9^2I_og{LqFh@OSkX@3GU*?b7%B0my*Z`l)e}ZM+QE)dCG*xPi);h{;fjw`~PsNnS91$v zFg~=-%a8V2_4y~$;V`09_AXdy&19`TR%l*-L|eB1W0q`Crw{urz?&~?K$Qy4!izu4 z&}vFtvvFna2jgi*@A_jjpSIS?(!u;uP&amv&cw-ZNbjXnNzhU{m1NF_J%%>LD=|*1 zm_b@A`^aJ7G9Lwn=OT6?jz$;(?!xQ&`p>w2s0e#1+q^BWjDZN8;x*)v?+iMXE9qnj zGX0mEV0PwhYiwmRP?NA zHBL07iiO_0K%QZv_&a%(ZbF_M=IY_^-7KW!yG~XNC5p#i9Z1;aM{~8=RhAzii0`Q` z-?YsVq9^^89hRzPb8erA`l@ms8-^HZV)&q9;mxVY;*aGe_A0YFUxV?VL==G1a<$G8;Bbyk2GY=8uU zR6Z$R4c%;tBU44tIMNOhIn8Q@j_nCsM`(9$Ufc=JURpwe*&P8oAg+iUZGG}+j(vT) zERPkeB%An}KNTlELj%B)vO*-nZ$Gh%`iUt`FD1I$0$%%hXCbTana$T}c4R-u z06$)|VQbpR!BLP+>X#5mkWTS^d(NM~Yx$7C%Lw6Y3)hF2cBq!r0XQ)QGtBxZ2eY@B z0^nR>FwWK>QuJNaAiG8S!HRT1>}x^cKB^; z0aq}F?7&i>t0=_2wlcE-Sz}56YG1ZM09}U>>fG~s7+8*;7j_6X&%;OX<-}VDX4oB} zYu{=S6Yu%MLOD;g!mL-02L`+z9Xt=FzJ+LU~JR z{QSHnCoeR%#9WN=%CIt8z0neCS!gsVR{a|Z?=krsrkJL2_nQyaV+SV!b}gp9$OijPqG1fHwnEik5xfj5RxUTQ%8f7NxCF_i#In`VH)-5myZ zcN^SY2X}Xu0S@l&u7kU~ySuyFIXHvE&i#_z?A>IO`q4>OQvbR;U2i?F@PMZdQy2Gj zJBO|7{;s9i;ia1dIZyZfet1#jVHp>15@8IaXwm$Q7;|n;f>NBqtRkdenJY5wwBiDwddK>He`yd{1y#CJB~ ztzd){8Hj&ai_iL#q;8)-())-pPzS_V5mfiF3lV51P~1wYfNCR-lHn!iHV%COUCk}W z3yhHA8eBW^uyBbE#@m`~2(cvLUuG=04QjB&ic557pO$M>N0*0^8$xv4R+8=mFX>>& z=zB&DSz+VEQ-gF0^TI+mHVmi1jZS278@Qq8yDS0z!#eJqAFF#;g?YTE_gjM2SRB* zjLCZ;MM_Ud_`uwmtdaAtBzSL3Fy)Eww1MdbmDfU4@ie`2jEiOGB&v3lVIj2pFZm68!^+_)G(2R|1B!3j+o zb0h1wgz^UK@*2$pO>jfb{mt+oBceL6)*<7+Awf#IYe{EnlyPKkmZ{YP zE@i@rTtrSKQfdp1!7=Ot7=jqx3fn^B_L+(?TIeUW4*)XXwg~;3Sx|MA>D1}5exoi_ zn)d9O89dwe8)bs#H6g`d$c4jAQNq?2TuRtB8wuGznh5 zI8NP`=3*`f<*o&5z-16JYe%hX@RiEUBPWJxy7XGwp>~=#jz<(nUdFk9>L0$_;81Oh zH#OK%p5!IF3|u4H5~mQJY%(&BigVmj!ktPJ#U}0I?sm=wDs#{LVrf(X1(6w@vFJwM z8(2YF%LbuqSf3mMVwohUXFuoEkDudW%N0q)uC8BJj7aWyha?J*qh9G8P%wrqau5Ih z8K=D#@2RpCkCd<`TWBsFQ>%au-)n4`>4(2_}FV|ms&x@h3UNE}=x zE2BqVeBV6hSh}zS>Rj4)r2B+%zG@J4Vp=9zdre2FYm{ zG}bj70S`AbjMST8kHWR%$7vi zSxgeO;D4S0-XW$*tUQP+$@1eg*jFmu4Y{u#IHY8X721@;m{fs;K&3fL4+|@M1?vpC_uS2+0Uingq?{E(|FDVBmPylv70 z|HdmKN_6vt)K@N3w|CT3+A)OGilXVLC!h}NPrq=PMkq{A!qIo0jD z0%=P(P0)|=(HpwMhtbb%KGp;KFVP;GmK1#b%%;^lUZ0gS`HElust4m)cFP?7tyKk| ziio<`!jg))67y%sp{U38peMf4Z4VN%v~N@bexH--Fm!9=Y71a~g@U0_P>rzJwied=)%#0IHWg#TwrjxJ&mr$P zu#8hZvrnTkFKRi!?n#>uODd~_0I}oRiI>`9*b-51V&$gHDz13@8&tlj9QJEh zd8A6mP^61jax5TQ9mGf7J7V?*=(xQrDHa(D_go$(BjEqMBy_mE5@n6CAkM}$WaG(R zE7j=%PdR9bI&?T5SL-4v%a}2a2G(c+KDRv@i)j=jUx)Xud8h*kTM*vHnh|O^xTEk+ zA0X;xb(RRggj+Z@1i^s!SeRmb!7*7mAhsobwIgRvBwkt8IE&&5MbG7FZvv1A(;^F`EU(1W2l6~5}3$woGdHCr}OVO1Ls zbe#>^RVm-jF9TF1yk=$3dY0#N&YRpSm{WDR_rGIqf0N&o;`qfI*u`MdvvnirNY3ux zytMjOZoRPJ)}b~gqiPGjlLit5be`j1yDfx>COK#Y$woM;x;6yVYTJ`iuq-GoFs|#M z73Q3Az=|Q7HL({wJ}ygh`})$Lf8JRxHPAB*wN60qtd*%serGC2G8`t__72mZAdOc@ z?ZKNx>a2WCn@EbZc(KW~e2CyPM4Jq_GymyOl8AS;62l;l0h{-|q5*CL0Q4d_SR*kW z?t&4}iGnFxVs#`R(Prb;TXyY5*tArqJPqJ%bxef*ZU5v?!!e16)#WL}=>0Ej1N4rM z9FNU}E30E_# zug}5jH+{{2w8c2Pk-Pz$^qFVbJ&fm-v#lvIsr_hCfdKtGMZ!qi$l`8Z7}7Qd)as62 zMX^ebJN5R=xQ(qIj&P1(vk*lsRcy6nZyHH*=X?k5Yc6QLsXD=Y(7L4lEUS!Vg>WR1 zo)3x(duXFm6}b0j3;bOzb1ofHHJ$QT#RJAlM;5v2i-`SqGG{`UQ(W~UP? z-hNi#Ek*y{P=l90$0!x)2_*Ft%<1Ce|Qo&fh!tZ2YN`D7aU=cOskkO(@$P z8&qGxVD>C8MpM+47AC(~X(CaF;>d6B1kk7iE`QlvncM>!mIrZG`n$sp9_Vt<1{Xu0 zKXk8ySNFuk9drQOn9OrK8TJ6WJF93Xv{f1%+(+XHbzya(7?q$qR>v8RQ;VR~gXBVS z9g01|o6g?5EU4jDSJtK4nW_g*xDvO*Qr=o^0bl3|gYT88S_hi4maI0_z3AfSdle{` z;L1Yf^h4cZh1+DCAEVMCz#9Gj0=eXHO)OZu4$KllZ6N?;=!`^-UqsadZx#6)#rtrT zQ3ye{nP~>r zWRc=L5)$_8{$w=%K4`DFbCtwtb>27dQe zlNcwvLmptRGFU*V+Fq)4*lt@BoH>qx7V7Y`7L#^->07H`Cy91$oqg>+J4r$qx2k&39*HdNdtlym5t zKy|mutpax?{c9%W7M?cR_)WB&^xqP-WGE$C_`!hSd-bp3=?Sovjb~NxNjO3O>-?SL zxZ02d$ZfDY*L>P)mL>YGkCOQq>wRpy${(tzbikk#{SDQoYU2;v`ckYKU`RStRdWrg z-xy!YP-Q%AI#MGANj#yUC>RSUlDQw+tNu_EiD)vn{E3=V-i!$5Z_IA~pCa+s-2JR@ z&wYS+`p7tYFte>z>%VXH3hm`@cJ@lQ+0e|K{F-ECxT+M0LBtdBy31GlK4$SQnco*KyM;$|X9@xG8@3mk#lWtoNK$0ZWMPEqHUbyvLN&LO<F6S-0Z_n(TMlwlV$9fQR ztl?(C2XqfZ33qnyQf|b)^oRYjb9g}Nt_8hT>#c;z@FSWpd?wZs|CtNBLXlm`2*6~! zA*Exsj;2*4z9;cgrkNEnlCOP4g2-)J@l7D3oQmx+EWI4tXY(b06!Zn8I&Etu0>m+; zmA`AZKcj+yEPCL7m6i>MVY$9@!$)eji>iM<8Y2pyLmhZB#IdDaN%62%dAq$-%n^wI{( z@3qx*spz$ou|d3X5~}06 z7wqv_qq8E~JfjMs0Ilb25g>54R|_I+AMxh%9YW_($RBvOQxO+zUt}~m|1%#)vy^yt zX~({C0RVi0f0-nZdI^s-;nlE zR~p0V@|zfix1T-Zc%Tm^-Lwt({?w>v*7?2yGWQ3wqEQL8PwU)pPs^iSh4=CeDWsI+ zOE9%Rd}^(q*<927^W`q`o9V_(<90+~j)K4oZs-S`PR@s3EW8R_M@w<8d)~q#NyK_^)*e%ecFWU> zzEms-w=P(pp~X)W|DvV!Z5Qz0U8kPrZFke$lQ2^g=3Iy80K*Pjq>MHcT;(;cnI!k{ zs?7dvUE!iIxKlOm(gB{)5Er>H1@wb4@U(5QAsV9_T(u4$<@I%_;+UK6m<0w>vre_~ zmeAlBBv)aba0WN4OflL0yEtZ5*q_(1AFs!)$oyjgH+6w`+f`Bw!eX&3iDD?c^zo-r zL`R1~TX#Gx0Gtu&$UV&ji3a-3-|dt>;ePRmcMZM1&viH~OVOCeAL~YS6$L=~`7!ul zAcxMF$E5NIF~ld3!|wvN`_v-!$GgyL=wd9jV0Ojm(P9=*7@H}dnqwDfX%wA~mRz`k zT40^_<}fO?Wlk`~Ri`$nt6q;z(sVqCP#@ld@H&(MaG@O=H?X0-h?Mw(e~2^3E0rV? zQN=+k(>zXQx?>beNjmdY$U zDJHL*+u`5LSH978H&o+((Y(MiGbyKl7H!FUi)IPyw4j5|!5A^I|BI}80j^E*T}Zt% zxK}V}4hYvRxpF$Rg|pYNlkEJV>}{&c-_X+sK&F_F#aOx?B}_gl8FsX1gZPS zCQt@4U4;QndET&1d_OZweJTumf^uo0m9!~lSG~R1+6fSX%#$g4pV%mIz{mUV$4Qj| zB0!4TKV`YTP}oLPu=Cr~7*~eu&n3tnBdVdULfLebVgzJNcjwIkkzF_C!`gxo?3wrB z4uBM;1wtmwNJXz-HaZKn|x1wA@gYf+jA_Q{ZvvTo9S@9!m~V`zID~hcAytg9R|}@q6fnd zDlo7|Ro5}?twt-t1Ch-vThaDG=-cqGrl!!p|FqO}=-kA;2BT3*reDF^chU|3g+!l6 zba%mvKBEu#cVY}nk0g}^1aj;LTnybS&^Y%O8`jgHr1k5)xXt|8r#tiX*jeBATZ+br z_aTe;m;+Gf3((yW(IV)zu6U1(s(kTib*(v3;fDpokD}|Xd5S95dT!jUNK!V_F<~oQ zk86>^KQb1hh$({&Ih0Yc6rv{pY=VL!Y7|xL=w^?)M+rKEa?7^Cpg=t+O%#|?6BO#L z??HesP2ftn6?lPba$ZgIrlfXR%Pw=x$l?_?=Lt4Q!-Ub9r{QO4BZbbwd+qqy;N1ZN z!(6fa3b+7MhYIx_F)M-Hlwj@#F8$tRKZAJ&w$E4A=X0^nN}V5*ZLtPGbhg=X+h7Mr zWhtGj1t$DDOxjm+xBmMSqWv#I3X@as6dzwB^g@S2)kUnfKaH;je8=XVy19cs<&bOCs=oePxpMome1{60&@y1aK+{B{NBW9vR>DRF;=tv z*Xtb!BkSh#yB3WOFhK}84t?#EC^oI?Q={SMy!m%K4JEH1OUc@dn({?`dWxyxmy`N% zgvh4I{Q<-Gkta_81_9T~Kl4jkLmKt`TO-j zF;j;5idh9RL{bsY;>e2dlw*B2IzcyGo1FUyrT8S)uN8dr#lDGKrFLfFuw2@fUO6m& zz3OeH?FiAOgfo8ZUyFw4B5H(C%Se8d%sRD-vFK?*m2vOs%Rc+?F~h3u2&;3DTg*sN zUyn*3Man*a?+3_uLvKN}#>|INvDj=U{-Ob3&FNg2Fr%g1nlYZ0?t=~@QHD`lbk=*9 zW1=(H1!mKs-NwMYwT}z=mNtu+@tEArM`-B~!|fDZ8}KI*6|VP|n1>pHtCszH_Ml81 z@gtJLJ?);=i2+p?dZp=2*mapHM;rR-7bQtV^1WGrlOZka!L;YB-44GvZ7W|OR*?WI zhO|uCcFiL;>Skl16bDE_J!f$KDOQ#yD4M5pWqdQW$Z%(miA{7`FoK z^^E#^p<6Z;MhG05|1BfO<7kP<)~yxfd+7spOVxZ{!-4>k?vsCh0P&$3a)cUbrUWh9 zjprah{uvc-_*x}-lm-JwjVd|fr}Rz%>!Lplel9R*&9QXsA|@o#j5+9gCd6C^m~3Oe z3cALV_CNraVv~ZtWkylx+E%Kka+W~@UV>1AIwUdH;qL|&h;SiM9Pc1W(7l=DA%uP835kT_Z6L5a| z=%45n+mD`Z#g|6azTPXAd2V>s-lF>Fu6U)VrzT`J{`Urr2 zPnl&_Sy?s}*dkI%=g!vIId&>mAn&x=t9v71Nk6Sj15*@shjq1NCqJ(ns|p)NJ%RNf z<^_p3f`Ua&Vt(zd;;zaB%<~mB5G{rMo6MGWu6C&Z`X7a4Kv$pqdX`%lIA=pndnx;o7UtJG(8;%p$GoJItIY2{~@Wn z?2JUG-j~M6NFGkYnx<#aV?}LBc*VU_k@hGtJ|!*Ql!B&SOt{cdWkG^zzO>`QYo<#~ zR_fG4uBnlT8nz|ct|ynmJg%<|aa7S|5ix zqOJ@27C823Y|w7FR!^f2Lk8$YZf#RvXeYO1VYoCuz)|5S+9jwfvrS6@-PwSr7HUVv zfidVb>fYg`&s(w{sjC^vBB=#*;3_&Xv=-0HeL}gnKM>M0;Rf!Zq4_PN!MI@~;;eb{ zWgtSMpXEkff7H8cNK9Abajb{kLkUzLvxN_G7`S+D>XQGY?ugQl(g953#(0kISP|QN z(I@tBeIuknT-q(G{F|F8X@@Px;XLtzhKvts_hCw%O30PTK83$-_?cjEwJMF9J*WCG zRX$2BJg-Dr*jpp~H|RO2)Loa^$*DPey7$r@kUa}s$=f@R*)D*iRKxtVk8clQcExGj z!a&p^+OT1rjaqfmRRTEH(9BT>sGsO=W>-SBvz{4yz~Mh^#?Pi zRV*g8lMn~L%`IJp@w}KtXL331XbtOf4Vp6^@wHugbI7|E-v*qY-6wJRWZ~WA&3gV& z&LzwGDQPYB?t7I6l>H`NM$tJYJ!p6NGwm=-IR?^D9x+=25JDPws;`Ka$P2135`UeTp!179+Q!{yp@sVLiN zNZH~-Wr5#I9L?NxKrGvRjAB$YRh7bEj-hwQU7(Wq^{? za?UL!Sojq^Bp7UK5>u2%pTiIGtR?8Ct0t~o#w6{-YEwTz|BoQeweErU5)urIKj}>q zAMpQSsdsDIDIRg6e9qL?A(ay(2b*DEZB)fm|E8+JtX9}o?^XNQ8`_D9LYHhA1UVn{ zdH}n;(63XA%7pz*;_@=Q_}Ox^bFvBY(Zwjg`348z8XT&WiWC|+N?>LlvQnwhKAi5J zd}uFU_XE%@q8$dZFM5GBVi@@28s!p=?ho$n(IZ9oPXhFGvsxl9;`5->&M?@iY(j$U zoczj4I@d|@Xr9bV#4vgD5&BS55#sq(h~QMx)#g{2?iaBah-Lczvwze#uF|p`ZgIF% zS|zoHzMY)NWt9*#2^StsPV zuv@-+lTsDkIU$up_DM}t8FJNO$%euS+FB0(>L;niW_KlI$-ho^y3Es7vmK&#ln%wz zQNI6(8K^Lz(Abg8Sa?xMfya={#waCv?(=I6v#uNP=+>8PX_<5h5AKpK2WR6|EYD@F zY(TOKEN+2FovtbmC|e3>Qh*P{r=O=EMjNoHadK9KeMQoOqvjzZFpf6s4kedw@(K$I z?w#H-U%^`-I*9vwKEv@m5U$WazoAHMpBxF@0nx49$SW3rUh>0La{o7%qcM_Bi6AGz zDPS~M*xr@k;X{7PmclGpZVxH&ahwU87SJ(ac?FfQkbIY(<_e8Y^iTft-itOriX;yE z4_<*DKJn+Z)h!XnWYjb82Tk!%{^J3L-t!N65smN(cxjkh(OAX#HRN(Q6gTTpr2AK8 z^QJTc*(|9EX(y`30hdgWlzuSxvXEjv!g2}t_o6!KWll{SLM86{9c=ZO{1kjhIlw={ zBi}unt2Ksxi#_5VURif+tUK>3z^S?Kwy~_@xGJ6D8*J(kKHZdk{_6M-Y+haHD*-Ou zUDrzurhusEtat}H8PR#@gtXu7<&YK6Eb@J_mggp2NMsxpd-zGEMp;!1rrfur_By;Z z*9)M)&AI#bsU%nP!hTtWR$a{8Ish~Jy~tVGToXRX4d(L3&cks2tItsn0qxq^AZXsv z#RBF@V&+j`o4(kNlui)~O`ESIV~kE+#njzDo|8`Y2U}Y=+SK{xy-CpvtY7mQ4Z8k{ zO3N;)Hxi*v<=+M^sO#6RacPsX~rLm?=h;Iz6LECfRApqr%y$rm9 z41V1Z3QNp!DmhM#VC5SK%H!1mxT#p~rcIdXC^{2BeCT>1Tfa+!eOM`)s7n+`_sB&2#HOC-!~CKqL7SrC#&D+U`^iGUbSQ8_Liuet3>W2L zE0>F?X%K8Q{=4)9w-aDIykMm|KW`Qm zW3||~Bv$iAnm)I33%1&ibub04+mtD*IipEKW72>R9KD~1F}a-LaSYFB2%MYF(wD5w zVr2<-4o)w zUjWsA94L~E0tra|v-RHsUz~qN7GPliEGfYL1e9_n6$Ie^?_{3;B>$ho7ZnT)8SMW~ wM$!ZOFRu#|mw#p#-~Y$#;y - - - - d3-ez : Reusable Components - - - - - - -
- - - - - diff --git a/package.json b/package.json index 05ed77c0..1b61026c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "d3-ez", - "version": "2.1.7", + "version": "2.1.8", "description": "D3 Easy Reusable Chary Library", "license": "GPL-3.0", "keywords": [ diff --git a/src/chart/circularHeat.js b/src/chart/circularHeat.js index 5fdda863..0aba1549 100644 --- a/src/chart/circularHeat.js +++ b/src/chart/circularHeat.js @@ -1,13 +1,6 @@ /** * Circular Heat Chart * - * @example - * var myChart = d3.ez.chart.circularHeat(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); - * - * Credit: Peter Cook http://animateddata.co.uk/ */ d3.ez.chart.circularHeat = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -15,27 +8,31 @@ d3.ez.chart.circularHeat = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartCircularHeat"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 20, left: 20 }; var transition = { ease: d3.easeBounce, duration: 500 }; - var classed = "chartCircularHeat"; var colors = [d3.rgb(214, 245, 0), d3.rgb(255, 166, 0), d3.rgb(255, 97, 0), d3.rgb(200, 65, 65)]; - var radius = undefined; - var innerRadius = undefined; - // Data Options (Populated by 'init' function) + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + + // Scales and Axis + var xScale + var yScale; + var yScale2; + var colorScale; + + // Data Variables + var categoryNames = []; + var groupNames = []; var minValue = 0; var maxValue = 0; - var radialLabels = []; - var numRadials = 24; - var segmentLabels = []; - var numSegments = 24; - var segmentHeight = 0; - - var colorScale = undefined; var thresholds = undefined; - var categoryNames = []; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); @@ -44,9 +41,16 @@ d3.ez.chart.circularHeat = function module() { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = (typeof radius === 'undefined') ? defaultRadius : radius; + innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 4 : innerRadius; + // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; // If thresholds values are not already set // attempt to auto-calculate some thresholds. @@ -55,9 +59,29 @@ d3.ez.chart.circularHeat = function module() { } // Colour Scale - colorScale = d3.scaleThreshold() - .domain(thresholds) - .range(colors); + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleThreshold() + .range(colors) + .domain(thresholds); + } + + // X & Y Scales + xScale = d3.scaleBand() + .domain(categoryNames) + .rangeRound([0, chartW]) + .padding(0.1); + + yScale = d3.scaleBand() + .domain(groupNames) + .rangeRound([radius, innerRadius]) + .padding(0.1); + + yScale2 = d3.scaleBand() + .domain(groupNames) + .rangeRound([-innerRadius, -radius]) + .padding(0.1); } function my(selection) { @@ -81,26 +105,36 @@ d3.ez.chart.circularHeat = function module() { .attr("height", height); chart = svg.append("g").classed("chart", true); + chart.append("g").classed("circleRings", true); + chart.append("g").classed("circleLabels", true); + chart.append("g").classed("axis", true); } else { chart = svg.select(".chart"); } // Update the chart dimensions - chart.classed("chartCircularHeat", true) + chart.classed(classed, true) .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")") .attr("width", chartW) .attr("height", chartH); - var heatMap = d3.ez.component.heatCircle() - .width(chartW) - .height(chartH) + var heatRing = d3.ez.component.heatRing() + .radius(function(d) { return yScale(d.key) }) + .innerRadius(function(d) { return yScale(d.key) + yScale.bandwidth(); }) .colorScale(colorScale) - .radius(radius) - .innerRadius(innerRadius) + .yScale(yScale) + .xScale(xScale) .dispatch(dispatch); - chart.datum(data) - .call(heatMap); + var series = chart.select(".circleRings").selectAll(".series") + .data(function(d) { return d; }) + .enter().append("g") + .attr("class", "series"); + + series.datum(function(d) { return d; }) + .call(heatRing); + + series.exit().remove(); // Circular Labels var circularLabels = d3.ez.component.circularLabels() @@ -108,9 +142,15 @@ d3.ez.chart.circularHeat = function module() { .height(chartH) .radius(radius); - chart.datum(categoryNames) + chart.select(".circleLabels") + .datum(categoryNames) .call(circularLabels); + // Y Axis + var yAxis = d3.axisLeft(yScale2); + chart.select(".axis") + .call(yAxis); + }); } diff --git a/src/chart/discreteBar.js b/src/chart/discreteBar.js index 60df827f..c22b32fd 100644 --- a/src/chart/discreteBar.js +++ b/src/chart/discreteBar.js @@ -1,15 +1,6 @@ /** * Discrete Bar Chart * - * @example - * var myChart = d3.ez.chart.discreteBar() - * .width(400) - * .height(300) - * .transition({ease: "bounce", duration: 1500}) - * .colors(d3.scaleCategory10().range()); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.discreteBar = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -17,35 +8,47 @@ d3.ez.chart.discreteBar = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartDiscreteBar"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 20, left: 40 }; var transition = { ease: d3.easeBounce, duration: 500 }; var colors = d3.ez.colors.categorical(4); - var gap = 0; - - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; - var yAxisLabel = undefined; + + // Chart Dimensions + var chartW; + var chartH; + + // Scales and Axis + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = undefined; + function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var categoryNames = slicedData.categoryNames; - var maxValue = slicedData.maxValue; - var yAxisLabel = slicedData.groupName; + categoryNames = slicedData.categoryNames; + maxValue = slicedData.maxValue; + yAxisLabel = slicedData.groupName; + + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal() + .range(colors) + .domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand() @@ -60,14 +63,6 @@ d3.ez.chart.discreteBar = function module() { // X & Y Axis xAxis = d3.axisBottom(xScale); yAxis = d3.axisLeft(yScale); - - if (!colorScale) { - // If the colorScale has not already been passed - // then attempt to calculate. - colorScale = d3.scaleOrdinal() - .range(colors) - .domain(categoryNames); - } } function my(selection) { @@ -98,7 +93,7 @@ d3.ez.chart.discreteBar = function module() { } // Update the chart dimensions - chart.classed("chartDiscreteBar", true) + chart.classed(classed, true) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .attr("width", chartW) .attr("height", chartH); diff --git a/src/chart/donut.js b/src/chart/donut.js index faa10fe2..c65b1754 100644 --- a/src/chart/donut.js +++ b/src/chart/donut.js @@ -1,15 +1,6 @@ /** * Donut Chart * - * @example - * var myChart = d3.ez.chart.donut() - * .width(400) - * .height(300) - * .radius(200) - * .innerRadius(50); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.donut = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -17,18 +8,24 @@ d3.ez.chart.donut = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartDonut"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 20, left: 20 }; var transition = { ease: d3.easeCubic, duration: 750 }; var colors = d3.ez.colors.categorical(4); - var radius = undefined; - var innerRadius = undefined; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + + // Scales and Axis + var colorScale; + + // Data Variables + var categoryNames = []; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); @@ -37,10 +34,15 @@ d3.ez.chart.donut = function module() { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = (typeof radius === 'undefined') ? defaultRadius : radius; + innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 2 : innerRadius; + // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var categoryNames = slicedData.categoryNames; + categoryNames = slicedData.categoryNames; + // Colour Scale if (!colorScale) { // If the colorScale has not already been passed // then attempt to calculate. @@ -76,15 +78,13 @@ d3.ez.chart.donut = function module() { } // Update the chart dimensions - chart.classed("chartDonut", true) + chart.classed(classed, true) .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")") .attr("width", chartW) .attr("height", chartH); // Add the chart var donutChart = d3.ez.component.donut() - .width(chartW) - .height(chartH) .radius(radius) .innerRadius(innerRadius) .colorScale(colorScale) diff --git a/src/chart/groupedBar.js b/src/chart/groupedBar.js index 443fa4bc..0cd17157 100644 --- a/src/chart/groupedBar.js +++ b/src/chart/groupedBar.js @@ -1,15 +1,6 @@ /** * Grouped Bar Chart * - * @example - * var myChart = d3.ez.chart.groupedBar() - * .width(400) - * .height(300) - * .transition({ease: "bounce", duration: 1500}) - * .groupType("stacked"); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.groupedBar = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -17,38 +8,57 @@ d3.ez.chart.groupedBar = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartGroupedBar"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 20, left: 40 }; var transition = { ease: d3.easeBounce, duration: 500 }; var colors = d3.ez.colors.categorical(4); - var gap = 0; - var yAxisLabel = null; - var groupType = "clustered"; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var xScale2 = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + + // Scales and Axis + var xScale; + var xScale2; + var yScale; + var xAxis; + var yAxis; + var colorScale; + + // Data Variables + var groupNames; + var groupTotalsMax; + var maxValue; + var categoryNames; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = null; + var groupType = "clustered"; + function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var groupNames = slicedData.groupNames; - var groupTotalsMax = slicedData.groupTotalsMax; - var maxValue = slicedData.maxValue; - var categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; + groupTotalsMax = slicedData.groupTotalsMax; + maxValue = slicedData.maxValue; + categoryNames = slicedData.categoryNames; + + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal() + .range(colors) + .domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand() @@ -68,11 +78,6 @@ d3.ez.chart.groupedBar = function module() { // X & Y Axis xAxis = d3.axisBottom(xScale); yAxis = d3.axisLeft(yScale); - - // Colour Scale - colorScale = d3.scaleOrdinal() - .range(colors) - .domain(categoryNames); } function my(selection) { @@ -109,7 +114,7 @@ d3.ez.chart.groupedBar = function module() { } // Update the chart dimensions - chart.classed("chartGroupedBar", true) + chart.classed(classed, true) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .attr("width", chartW) .attr("height", chartH); @@ -173,18 +178,18 @@ d3.ez.chart.groupedBar = function module() { return this; }; - my.yAxisLabel = function(_) { - if (!arguments.length) return yAxisLabel; - yAxisLabel = _; - return this; - }; - my.groupType = function(_) { if (!arguments.length) return groupType; groupType = _; return this; }; + my.yAxisLabel = function(_) { + if (!arguments.length) return yAxisLabel; + yAxisLabel = _; + return this; + }; + my.transition = function(_) { if (!arguments.length) return transition; transition = _; diff --git a/src/chart/multiSeriesLine.js b/src/chart/multiSeriesLine.js index f9386e94..ed01de1d 100644 --- a/src/chart/multiSeriesLine.js +++ b/src/chart/multiSeriesLine.js @@ -1,13 +1,6 @@ /** * Multi Series Line Chart * - * @example - * var myChart = d3.ez.chart.multiSeriesLine() - * .width(400) - * .height(300); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.multiSeriesLine = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -15,33 +8,42 @@ d3.ez.chart.multiSeriesLine = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartMultiSeriesLine"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 40, left: 40 }; + var transition = { ease: d3.easeBounce, duration: 500 }; var colors = d3.ez.colors.categorical(3); - var yAxisLabel = null; - var groupType = "clustered"; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + + // Scales and Axis + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; + + // Data Variables + var maxValue; + var groupNames; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var yAxisLabel = null; + function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var seriesNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + groupNames = slicedData.groupNames; // Convert dates data.forEach(function(d, i) { @@ -51,6 +53,15 @@ d3.ez.chart.multiSeriesLine = function module() { }); dateDomain = d3.extent(data[0].values, function(d) { return d.key; }); + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal() + .range(colors) + .domain(groupNames); + } + // X & Y Scales xScale = d3.scaleTime() .range([0, chartW]) @@ -64,11 +75,6 @@ d3.ez.chart.multiSeriesLine = function module() { xAxis = d3.axisBottom(xScale) .tickFormat(d3.timeFormat("%d-%b-%y")); yAxis = d3.axisLeft(yScale); - - // Colour Scale - colorScale = d3.scaleOrdinal() - .range(colors) - .domain(seriesNames); } function my(selection) { @@ -105,7 +111,7 @@ d3.ez.chart.multiSeriesLine = function module() { } // Update the chart dimensions - chart.classed("chartMultiSeriesLine", true) + chart.classed(classed, true) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .attr("width", chartW) .attr("height", chartH); @@ -178,12 +184,6 @@ d3.ez.chart.multiSeriesLine = function module() { return this; }; - my.groupType = function(_) { - if (!arguments.length) return groupType; - groupType = _; - return this; - }; - my.transition = function(_) { if (!arguments.length) return transition; transition = _; diff --git a/src/chart/punchCard.js b/src/chart/punchCard.js index 83d177de..294c3d56 100644 --- a/src/chart/punchCard.js +++ b/src/chart/punchCard.js @@ -1,17 +1,6 @@ /** - * Punchcard + * Punch Card * - * @example - * var myChart = d3.ez.chart.punchCard() - * .width(600) - * .height(350) - * .color("green") - * .minRadius(5) - * .maxRadius(19) - * .useGlobalScale(true); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.punchCard = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -19,45 +8,65 @@ d3.ez.chart.punchCard = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartPunchCard"; var width = 400; var height = 300; - var margin = { top: 50, right: 40, bottom: 40, left: 40 }; + var margin = { top: 45, right: 20, bottom: 20, left: 45 }; var transition = { ease: d3.easeBounce, duration: 500 }; - var color = "steelblue"; - var sizeScale = undefined; + var colors = [d3.rgb("steelblue").brighter(), d3.rgb("steelblue").darker()]; + + // Chart Dimensions + var chartW; + var chartH; + + // Scales and Axis + var sizeScale; + var xScale; + var yScale; + var xAxis; + var yAxis; + var colorScale; + + // Data Variables + var maxValue; + var minValue; + var categoryNames; + var groupNames; + + // Dispatch (Custom events) + var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + + // Misc Options var minRadius = 2; var maxRadius = 20; var formatTick = d3.format("0000"); var useGlobalScale = true; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; - - // Dispatch (Custom events) - var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); - function init(data) { chartW = width - margin.left - margin.right; chartH = height - margin.top - margin.bottom; // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var minValue = slicedData.minValue; - var categoryNames = slicedData.categoryNames; - var groupNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; + categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; valDomain = [minValue, maxValue]; sizeDomain = useGlobalScale ? valDomain : [0, d3.max(data[1]['values'], function(d) { return d['value']; })]; + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleLinear() + .domain(valDomain) + .range(colors); + } + // X & Y Scales xScale = d3.scaleBand() .domain(categoryNames) @@ -73,11 +82,6 @@ d3.ez.chart.punchCard = function module() { xAxis = d3.axisTop(xScale); yAxis = d3.axisLeft(yScale); - // Colour Scale - colorScale = d3.scaleLinear() - .domain(valDomain) - .range([d3.rgb(color).brighter(), d3.rgb(color).darker()]); - // Size Scale sizeScale = d3.scaleLinear() .domain(sizeDomain) @@ -86,9 +90,6 @@ d3.ez.chart.punchCard = function module() { function my(selection) { selection.each(function(data) { - // If it is a single object, wrap it in an array - if (data.constructor !== Array) data = [data]; - // Initialise Data init(data); @@ -115,10 +116,10 @@ d3.ez.chart.punchCard = function module() { } // Update the chart dimensions - chart.classed("chartPunchCard", true) + chart.classed(classed, true) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") - .attr("width", width) - .attr("height", height); + .attr("width", chartW) + .attr("height", chartH); // Add axis to chart chart.select(".x-axis") @@ -186,18 +187,18 @@ d3.ez.chart.punchCard = function module() { return this; }; - my.color = function(_) { - if (!arguments.length) return color; - color = _; - return this; - }; - my.sizeScale = function(_) { if (!arguments.length) return sizeScale; sizeScale = _; return this; }; + my.colors = function(_) { + if (!arguments.length) return colors; + colors = _; + return this; + }; + my.useGlobalScale = function(_) { if (!arguments.length) return useGlobalScale; useGlobalScale = _; diff --git a/src/chart/radialBar.js b/src/chart/radialBar.js index fabcf67b..c51e149b 100644 --- a/src/chart/radialBar.js +++ b/src/chart/radialBar.js @@ -1,13 +1,6 @@ /** * Radial Bar Chart * - * @example - * var myChart = d3.ez.chart.radialBar(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); - * - * Credit: Peter Cook http://animateddata.co.uk/ */ d3.ez.chart.radialBar = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -15,35 +8,57 @@ d3.ez.chart.radialBar = function module() { var chart; // Default Options (Configurable via setters) + var classed = "chartRadialBar"; var width = 400; var height = 300; var margin = { top: 20, right: 20, bottom: 20, left: 20 }; var transition = { ease: d3.easeBounce, duration: 500 }; var colors = d3.ez.colors.categorical(4); - var radius = undefined; - var capitalizeLabels = false; - var colorLabels = false; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var yScale = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + var radius; + var innerRadius; + + // Scales and Axis + var xScale + var yScale; + var yScale2; + var colorScale; + + // Data Variables var categoryNames = []; var maxValue = 0; // Dispatch (Custom events) var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + // Other Customisation Options + var capitalizeLabels = false; + var colorLabels = false; + function init(data) { chartW = width - (margin.left + margin.right); chartH = height - (margin.top + margin.bottom); + var defaultRadius = Math.min(chartW, chartH) / 2; + radius = (typeof radius === 'undefined') ? defaultRadius : radius; + innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 4 : innerRadius; + // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - maxValue = slicedData.maxValue; - var domain = [0, maxValue]; categoryNames = slicedData.categoryNames; + maxValue = slicedData.maxValue; + + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleOrdinal() + .range(colors) + .domain(categoryNames); + } // X & Y Scales xScale = d3.scaleBand() @@ -52,13 +67,12 @@ d3.ez.chart.radialBar = function module() { .padding(0.15); yScale = d3.scaleLinear() - .domain(domain) + .domain([0, maxValue]) .range([0, radius]); - // Colour Scale - colorScale = d3.scaleOrdinal() - .range(colors) - .domain(categoryNames); + yScale2 = d3.scaleLinear() + .domain([0, maxValue]) + .range([0, -radius]); } function my(selection) { @@ -91,15 +105,13 @@ d3.ez.chart.radialBar = function module() { } // Update the chart dimensions - chart.classed("chartRadialBar", true) + chart.classed(classed, true) .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")") - .attr("width", width) - .attr("height", height); + .attr("width", chartW) + .attr("height", chartH); // Add the chart var barRadial = d3.ez.component.barRadial() - .width(chartW) - .height(chartH) .radius(radius) .yScale(yScale) .colorScale(colorScale) @@ -121,7 +133,6 @@ d3.ez.chart.radialBar = function module() { .call(circularAxis); // Y Axis - var yScale2 = d3.scaleLinear().domain(yScale.domain()).range([0, -radius]); var yAxis = d3.axisLeft(yScale2); chart.select(".axis") .call(yAxis); @@ -143,21 +154,18 @@ d3.ez.chart.radialBar = function module() { my.width = function(_) { if (!arguments.length) return width; width = _; - radius = d3.min([(width - (margin.right + margin.left)), (height - (margin.top + margin.bottom))]) / 2; return this; }; my.height = function(_) { if (!arguments.length) return height; height = _; - radius = d3.min([(width - (margin.right + margin.left)), (height - (margin.top + margin.bottom))]) / 2; return this; }; my.margin = function(_) { if (!arguments.length) return margin; margin = _; - radius = d3.min([(width - (margin.right + margin.left)), (height - (margin.top + margin.bottom))]) / 2; return this; }; diff --git a/src/chart/tabularHeat.js b/src/chart/tabularHeat.js index edfd3031..f4a9a051 100644 --- a/src/chart/tabularHeat.js +++ b/src/chart/tabularHeat.js @@ -1,11 +1,6 @@ /** * Tabular Heat Chart * - * @example - * var myChart = d3.ez.chart.tabularHeat(); - * d3.select("#chartholder") - * .datum(data) - * .call(myChart); */ d3.ez.chart.tabularHeat = function module() { // SVG and Chart containers (Populated by 'my' function) @@ -13,20 +8,29 @@ d3.ez.chart.tabularHeat = function module() { var chart; // Default Options (Configurable via setters) - var width = 600; - var height = 600; - var margin = { top: 50, right: 40, bottom: 40, left: 40 }; + var classed = "chartTabularHeat"; + var width = 400; + var height = 300; + var margin = { top: 45, right: 20, bottom: 20, left: 45 }; var transition = { ease: d3.easeBounce, duration: 500 }; var colors = [d3.rgb(214, 245, 0), d3.rgb(255, 166, 0), d3.rgb(255, 97, 0), d3.rgb(200, 65, 65)]; - // Data Options (Populated by 'init' function) - var chartW = 0; - var chartH = 0; - var xScale = undefined; - var yScale = undefined; - var xAxis = undefined; - var yAxis = undefined; - var colorScale = undefined; + // Chart Dimensions + var chartW; + var chartH; + + // Scales and Axis + var xScale; + var yScale; + var colorScale; + var xAxis; + var yAxis; + + // Data Variables + var groupNames = []; + var categoryNames = []; + var minValue = 0; + var maxValue = 0; var thresholds = undefined; // Dispatch (Custom events) @@ -38,10 +42,10 @@ d3.ez.chart.tabularHeat = function module() { // Slice Data, calculate totals, max etc. var slicedData = d3.ez.dataParse(data); - var maxValue = slicedData.maxValue; - var minValue = slicedData.minValue; - var categoryNames = slicedData.categoryNames; - var groupNames = slicedData.groupNames; + maxValue = slicedData.maxValue; + minValue = slicedData.minValue; + categoryNames = slicedData.categoryNames; + groupNames = slicedData.groupNames; // If thresholds values are not already set // attempt to auto-calculate some thresholds. @@ -49,6 +53,15 @@ d3.ez.chart.tabularHeat = function module() { var thresholds = slicedData.thresholds; } + // Colour Scale + if (!colorScale) { + // If the colorScale has not already been passed + // then attempt to calculate. + colorScale = d3.scaleThreshold() + .domain(thresholds) + .range(colors); + } + // X & Y Scales xScale = d3.scaleBand() .domain(categoryNames) @@ -63,11 +76,6 @@ d3.ez.chart.tabularHeat = function module() { // X & Y Axis xAxis = d3.axisTop(xScale); yAxis = d3.axisLeft(yScale); - - // Colour Scale - colorScale = d3.scaleThreshold() - .domain(thresholds) - .range(colors); } function my(selection) { @@ -98,7 +106,7 @@ d3.ez.chart.tabularHeat = function module() { } // Update the chart dimensions - chart.classed("chartTabularHeat", true) + chart.classed(classed, true) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .attr("width", chartW) .attr("height", chartH); @@ -133,6 +141,9 @@ d3.ez.chart.tabularHeat = function module() { .call(heatMap); series.exit().remove(); + + + }); } @@ -173,12 +184,6 @@ d3.ez.chart.tabularHeat = function module() { return this; }; - my.accessor = function(_) { - if (!arguments.length) return accessor; - accessor = _; - return this; - }; - my.dispatch = function(_) { if (!arguments.length) return dispatch(); dispatch = _; diff --git a/src/component/barStacked.js b/src/component/barStacked.js index 1d5eab46..9e829676 100644 --- a/src/component/barStacked.js +++ b/src/component/barStacked.js @@ -33,7 +33,7 @@ d3.ez.component.barStacked = function module() { y0 += d.value; }); - data = {key: data.key, values: series}; + data = { key: data.key, values: series }; return [data]; }) .enter() diff --git a/src/component/circularLabels.js b/src/component/circularLabels.js index e4271b7a..a355c377 100644 --- a/src/component/circularLabels.js +++ b/src/component/circularLabels.js @@ -17,7 +17,7 @@ d3.ez.component.circularLabels = function module() { selection.each(function(data) { var defaultRadius = Math.min(width, height) / 2; radius = (typeof radius === 'undefined') ? defaultRadius : radius; - var labelRadius = radius * 1.050; + var labelRadius = radius * 1.020; var circularLabels = selection.selectAll('.circularLabels') .data(function(d) { return [d]; }) diff --git a/src/component/donut.js b/src/component/donut.js index 320249be..63a4b2c7 100644 --- a/src/component/donut.js +++ b/src/component/donut.js @@ -22,15 +22,17 @@ d3.ez.component.donut = function module() { radius = (typeof radius === 'undefined') ? defaultRadius : radius; innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 2 : innerRadius; + // Pie Generator var pie = d3.pie() .value(function(d) { return d.value; }) - .sort(null); + .sort(null) + .padAngle(0.015); + // Arc Generators var arc = d3.arc() .innerRadius(innerRadius) .outerRadius(radius) - .cornerRadius(3) - .padAngle(0.015); + .cornerRadius(2); var outerArc = d3.arc() .innerRadius(radius * 0.9) diff --git a/src/component/heatCircle.js b/src/component/heatCircle.js deleted file mode 100644 index 8dbf006d..00000000 --- a/src/component/heatCircle.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Reusable Circular Heat Map - * - * @example - * var myBars = d3.ez.component.heatCircle() - * .colorScale(**D3 Scale Object**); - * d3.select("svg").call(myBars); - */ -d3.ez.component.heatCircle = function module() { - // Default Options (Configurable via setters) - var width = 400; - var height = 300; - var colorScale = undefined; - var transition = { ease: d3.easeBounce, duration: 500 }; - var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); - var radius = undefined; - var innerRadius = undefined; - - function my(selection) { - selection.each(function(data) { - - // Slice Data, calculate totals, max etc. - var slicedData = d3.ez.dataParse(data); - groupNames = slicedData.groupNames; - numRadials = groupNames.length; - categoryNames = slicedData.categoryNames; - numSegments = categoryNames.length; - - var defaultRadius = Math.min(width, height) / 2; - radius = (typeof radius === 'undefined') ? defaultRadius : radius; - innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 4 : innerRadius; - var segmentHeight = ((radius - innerRadius) / numRadials); - - // Arc Generator - var arc = d3.arc() - .innerRadius(function(d, i) { - return innerRadius + segmentHeight * d.ring; - }) - .outerRadius(function(d, i) { - return innerRadius + segmentHeight * (d.ring + 1); - }) - .startAngle(function(d, i) { - return (i * 2 * Math.PI) / numSegments; - }) - .endAngle(function(d, i) { - return ((i + 1) * 2 * Math.PI) / numSegments; - }); - - // Create chart group - var circularHeat = selection.selectAll('.heatCircle') - .data(function(d) { return [d]; }) - .enter() - .append("g") - .classed("heatCircle", true) - .on("click", function(d) { dispatch.call("customClick", this, d); }); - - circularHeat.append("g").attr("class", "rings"); - circularHeat.append("g").attr("class", "radialLabels"); - var circularHeat = selection.selectAll('.heatCircle').merge(circularHeat); - - // Rings - circularHeat.select(".rings").selectAll("g") - .data(function(d) { return d; }) - .enter() - .append("g") - .classed("ring", true); - - // Ring Segments - circularHeat.selectAll(".ring").selectAll("path") - .data(function(d, i) { - // Add index (used to calculate ring number) - for (j = 0; j < numSegments; j++) { - d.values[j].ring = i; - } - return d.values; - }) - .enter() - .append("path") - .attr("d", arc) - .attr("fill", function(d) { - return colorScale(d.value); - }) - .classed("segment", true) - .on("mouseover", function(d) { dispatch.call("customMouseOver", this, d); }); - - // Radial Labels - var lsa = 0.01; // Label start angle - var radialLabels = circularHeat.select(".radialLabels") - .classed("labels", true); - - radialLabels.selectAll("def") - .data(function(d) { - return d; - }) - .enter() - .append("def") - .append("path") - .attr("id", function(d, i) { - return "radial-label-path-" + i; - }) - .attr("d", function(d, i) { - var r = innerRadius + ((i + 0.2) * segmentHeight); - return "m" + r * Math.sin(lsa) + " -" + r * Math.cos(lsa) + " a" + r + " " + r + " 0 1 1 -1 0"; - }); - - radialLabels.selectAll("text") - .data(groupNames) - .enter() - .append("text") - .append("textPath") - .attr("xlink:href", function(d, i) { - return "#radial-label-path-" + i; - }) - .text(function(d) { - return d; - }); - - }); - - } - - // Configuration Getters & Setters - my.height = function(_) { - if (!arguments.length) return height; - height = _; - return this; - }; - - my.width = function(_) { - if (!arguments.length) return width; - width = _; - return this; - }; - - my.radius = function(_) { - if (!arguments.length) return radius; - radius = _; - return this; - }; - - my.innerRadius = function(_) { - if (!arguments.length) return innerRadius; - innerRadius = _; - return this; - }; - - my.colorScale = function(_) { - if (!arguments.length) return colorScale; - colorScale = _; - return my; - }; - - my.dispatch = function(_) { - if (!arguments.length) return dispatch(); - dispatch = _; - return this; - }; - - my.on = function() { - var value = dispatch.on.apply(dispatch, arguments); - return value === dispatch ? my : value; - }; - - return my; -}; diff --git a/src/component/heatMap.js b/src/component/heatMap.js index 999ebae9..fb7bccdb 100644 --- a/src/component/heatMap.js +++ b/src/component/heatMap.js @@ -8,8 +8,8 @@ */ d3.ez.component.heatMap = function module() { // Default Options (Configurable via setters) - var height = 100; var width = 300; + var height = 100; var colorScale = undefined; var xScale = undefined; var yScale = undefined; @@ -21,37 +21,37 @@ d3.ez.component.heatMap = function module() { var cellHeight = yScale.bandwidth(); var cellWidth = xScale.bandwidth(); - selection.selectAll('.deck') + var heatMap = selection.selectAll('.heatMap') .data(function(d) { return [d]; }) .enter() .append("g") - .classed('deck', true) + .classed('heatMap', true) .on("click", function(d) { dispatch.call("customClick", this, d); }); - var deck = selection.selectAll('.deck'); + heatMap = selection.selectAll('.heatMap').merge(heatMap); - var cards = deck.selectAll(".card") + var cells = heatMap.selectAll(".cell") .data(function(d) { return d.values; }); - cards.enter().append("rect") + cells.enter().append("rect") .attr("x", function(d, i) { return xScale(d.key); }) .attr("y", 0) - .attr("rx", 3) - .attr("ry", 3) - .attr("class", "card") + .attr("rx", 2) + .attr("ry", 2) + .attr("class", "cell") .attr("width", cellWidth) .attr("height", cellHeight) .on("click", dispatch.customClick) .on("mouseover", function(d) { dispatch.call("customMouseOver", this, d); }) - .merge(cards) + .merge(cells) .transition() .duration(1000) .attr("fill", function(d) { return colorScale(d.value); }); - cards.exit().remove(); + cells.exit().remove(); - cards.select("title").text(function(d) { + cells.select("title").text(function(d) { return d.value; }); diff --git a/src/component/heatRing.js b/src/component/heatRing.js new file mode 100644 index 00000000..7215e012 --- /dev/null +++ b/src/component/heatRing.js @@ -0,0 +1,130 @@ +/** + * Reusable Circular Heat Ring + * + * @example + * var myBars = d3.ez.component.heatRing() + * .colorScale(**D3 Scale Object**); + * d3.select("svg").call(myBars); + */ +d3.ez.component.heatRing = function module() { + // Default Options (Configurable via setters) + var width = 400; + var height = 300; + var colorScale = undefined; + var xScale = undefined; + var yScale = undefined; + var transition = { ease: d3.easeBounce, duration: 500 }; + var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); + var radius = undefined; + var innerRadius = undefined; + + function my(selection) { + selection.each(function(data) { + var defaultRadius = Math.min(width, height) / 2; + radius = (typeof radius === 'undefined') ? defaultRadius : radius; + innerRadius = (typeof innerRadius === 'undefined') ? defaultRadius / 4 : innerRadius; + + // Pie Generator + var pie = d3.pie() + .value(function(d) { return 1; }) + .sort(null) + .padAngle(0.015); + + // Arc Generator + var arc = d3.arc() + .outerRadius(radius) + .innerRadius(innerRadius) + .cornerRadius(2); + + // Create chart group + var heatRing = selection.selectAll('.heatRing') + .data(function(d) { return [d]; }) + .enter() + .append("g") + .classed("heatRing", true) + .on("click", function(d) { dispatch.call("customClick", this, d); }); + heatRing = selection.selectAll('.heatRing').merge(heatRing); + + var segments = heatRing.selectAll(".segment") + .data(function(d) { + var key = d.key; + var data = pie(d.values); + data.forEach(function(d, i) { + data[i].key = key; + }); + + return data; + }); + + // Ring Segments + segments + .enter() + .append("path") + .attr("d", arc) + .attr("fill", function(d) { + return colorScale(d.data.value); + }) + .classed("segment", true) + .on("mouseover", function(d) { dispatch.call("customMouseOver", this, d.data); }); + + segments.exit().remove(); + }); + + } + + // Configuration Getters & Setters + my.height = function(_) { + if (!arguments.length) return height; + height = _; + return this; + }; + + my.width = function(_) { + if (!arguments.length) return width; + width = _; + return this; + }; + + my.radius = function(_) { + if (!arguments.length) return radius; + radius = _; + return this; + }; + + my.innerRadius = function(_) { + if (!arguments.length) return innerRadius; + innerRadius = _; + return this; + }; + + my.colorScale = function(_) { + if (!arguments.length) return colorScale; + colorScale = _; + return my; + }; + + my.xScale = function(_) { + if (!arguments.length) return xScale; + xScale = _; + return my; + }; + + my.yScale = function(_) { + if (!arguments.length) return yScale; + yScale = _; + return my; + }; + + my.dispatch = function(_) { + if (!arguments.length) return dispatch(); + dispatch = _; + return this; + }; + + my.on = function() { + var value = dispatch.on.apply(dispatch, arguments); + return value === dispatch ? my : value; + }; + + return my; +}; diff --git a/src/header.js b/src/header.js index af147317..cdb30e9c 100644 --- a/src/header.js +++ b/src/header.js @@ -6,7 +6,7 @@ * @license GPLv3 */ d3.ez = { - version: "2.1.7", + version: "2.1.8", author: "James Saunders", copyright: "Copyright (C) 2017 James Saunders", license: "GPL-3.0" diff --git a/src/vega.js b/src/vega.js deleted file mode 100644 index e979e070..00000000 --- a/src/vega.js +++ /dev/null @@ -1,210 +0,0 @@ -/** - * Vega - * - * @example - * @todo - */ -d3.ez.vega = function module() { - // SVG and Canvas containers (Populated by 'my' function) - var svg; - var canvas; - - // Default Options (Configurable via setters) - var width = 1000; - var height = 600; - var margin = { top: 30, right: 30, bottom: 30, left: 30 }; - var padding = 20; - var canvasW = 580; - var canvasH = 380; - var chartTop = 0; - var classed = "d3ez"; - - var chart = undefined; - var yScale = undefined; - var yAxis = undefined; - var xScale = undefined; - var xAxis = undefined; - var legend = undefined; - var title = undefined; - var creditTag = d3.ez.component.creditTag(); - var yAxisLabel = ""; - - // Colours - var colorScale = undefined; - - // Dispatch (custom events) - var dispatch = d3.dispatch("customMouseOver", "customMouseOut", "customClick"); - - function init(data) { - canvasW = width - (margin.left + margin.right); - canvasH = height - (margin.top + margin.bottom); - - // Init Chart - chart.colorScale(colorScale) - .xScale(xScale) - .yScale(yScale) - .width(canvasW) - .height(canvasH) - .dispatch(dispatch); - - // Init Legend - if (legend) { - legend.width(120).height(200); - chart.width(chart.width() - legend.width() - padding); - } - - // Init Title - if (title) { - chartTop = title.height() + padding; - chart.height(chart.height() - title.height() - padding); - } - - // Have we changed the graph size? - xScale.range([chart.width(), 0]); - yScale.range([chart.height(), 0]); - - // Init Axis - xAxis = d3.axisBottom(xScale); - yAxis = d3.axisLeft(yScale); - - // Init Credit Tag - creditTag.text("d3-ez.net").href("http://d3-ez.net"); - } - - function my(selection) { - selection.each(function(data) { - init(data); - - // Create SVG element (if it does not exist already) - if (!svg) { - svg = d3.select(this) - .append("svg") - .classed(classed, true) - .attr("width", width) - .attr("height", height); - - canvas = svg.append("g").classed("canvas", true); - canvas.append("g").classed("chartbox", true); - canvas.select(".chartbox").append("g").classed("x-axis axis", true); - canvas.select(".chartbox").append("g").classed("y-axis axis", true); - canvas.append("g").classed("legendbox", true); - canvas.append("g").classed("titlebox", true); - svg.append("g").classed("creditbox", true); - } else { - canvas = svg.select(".canvas") - } - - // Update the canvas dimensions - canvas.attr("transform", "translate(" + margin.left + "," + margin.top + ")") - .attr("width", canvasW) - .attr("height", canvasH); - - // Add Chart - canvas.select(".chartbox") - .attr("transform", "translate(0," + chartTop + ")") - .datum(data) - .call(chart); - - canvas.select(".y-axis") - .call(yAxis); - - canvas.select(".x-axis") - .attr("transform", "translate(0," + chart.height() + ")") - .call(xAxis); - - // Add Title - if (title) { - canvas.select(".titlebox") - .attr("transform", "translate(" + width / 2 + "," + 0 + ")") - .call(title); - } - - // Add Legend - if (legend && (typeof chart.colorScale === "function" || typeof chart.sizeScale === "function")) { - if (typeof chart.colorScale === "function") { - legend.colorScale(chart.colorScale()); - } - if (typeof chart.sizeScale === "function") { - legend.sizeScale(chart.sizeScale()); - } - canvas.select(".legendbox") - .attr("transform", "translate(" + (canvasW - legend.width()) + "," + chartTop + ")") - .call(legend); - } - - // Add Credit Tag - if (creditTag) { - svg.select(".creditbox") - .attr("transform", "translate(" + (width - 10) + "," + (height - 5) + ")") - .call(creditTag); - } - }); - } - - // Configuration Getters & Setters - my.width = function(_) { - if (!arguments.length) return width; - width = _; - return this; - }; - - my.height = function(_) { - if (!arguments.length) return height; - height = _; - return this; - }; - - my.chart = function(_) { - if (!arguments.length) return chart; - chart = _; - return this; - }; - - my.colorScale = function(_) { - if (!arguments.length) return colorScale; - colorScale = _; - return this; - }; - - my.xScale = function(_) { - if (!arguments.length) return xScale; - xScale = _; - return this; - }; - - my.yScale = function(_) { - if (!arguments.length) return yScale; - yScale = _; - return this; - }; - - my.legend = function(_) { - if (!arguments.length) return legend; - legend = _; - return this; - }; - - my.title = function(_) { - if (!arguments.length) return title; - if (typeof _ === "string") { - // If the caller has passed a plain string convert it to a title object. - title = d3.ez.title().mainText(_).subText(''); - } else { - title = _; - } - return this; - }; - - my.yAxisLabel = function(_) { - if (!arguments.length) return yAxisLabel; - yAxisLabel = _; - return this; - }; - - my.on = function() { - var value = dispatch.on.apply(dispatch, arguments); - return value === dispatch ? my : value; - } - - return my; -}; diff --git a/test/DataTest.html b/test/DataTest.html index 79a23483..4e2aeb5c 100644 --- a/test/DataTest.html +++ b/test/DataTest.html @@ -4,13 +4,13 @@ d3-ez : Data Parse Test - + diff --git a/test/d3-ez.js b/test/d3-ez.js index 559dcbad..91c8628a 100644 --- a/test/d3-ez.js +++ b/test/d3-ez.js @@ -4,7 +4,6 @@ include('../src/header.js'); include('../src/baseFunctions.js'); include('../src/chart.js'); -include('../src/vega.js'); include('../src/colors.js'); include('../src/component/barGrouped.js'); include('../src/component/barStacked.js'); @@ -15,7 +14,7 @@ include('../src/component/labeledNode.js'); include('../src/component/legend.js'); include('../src/component/lineChart.js'); include('../src/component/heatMap.js'); -include('../src/component/heatCircle.js'); +include('../src/component/heatRing.js'); include('../src/component/punchCard.js'); include('../src/component/numberCard.js'); include('../src/component/scatterPlot.js');