Skip to content

Instantly share code, notes, and snippets.

@lorenzopub
Created March 18, 2017 12:29
Show Gist options
  • Save lorenzopub/43fca546e0073d4fc54723f06737d78d to your computer and use it in GitHub Desktop.
Save lorenzopub/43fca546e0073d4fc54723f06737d78d to your computer and use it in GitHub Desktop.
stacked bar chart with dynamic axes and labels
license: mit
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>http://stackoverflow.com/questions/32057842/d3-js-highlighting-stacked-bar-and-getting-selected-values/32079517#32079517</title>
<style>
body {
position: relative;
}
#vis {
margin: 100px;
position: relative;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.fly-in {
font-size: 8px;
}
.axis .tick line {
stroke: #ccc;
/*opacity: 0.5;*/
pointer-events: none;
}
/*.axis .minor line{*/
/*stroke: red;*/
/*}*/
.highlight {
font-weight: bold ;
}
svg {
overflow: visible;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="UTF-8"></script>
<!--<script src="https://rawgit.com/cool-Blue/d3-lib/516508b6aa8d9ae724ceb194257226aa29d48fb7/inputs/select/select.js"></script>-->
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/select/select.js" charset="UTF-8"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform-2.0.0/plot-transform.js" charset="UTF-8"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/transitions/end-all/endAll.js" charset="UTF-8"></script>
<script src="script.js"></script>
</body>
</html>
var width = 760,
height = 300,
padding = {left: 50, right: 200, top: 20, bottom: 30},
xRangeWidth = width - padding.left - padding.right,
yRangeHeight = height - padding.top - padding.bottom;
var vis = d3.select("body").append("div").attr({
margin: "auto",
id: "vis"
}),
svg = vis
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + [padding.left, padding.top] + ")");
var dataSet1 = [
{
name: "PC",
sales: [{year: 2005, profit: 3000},
{year: 2006, profit: 1300},
{year: 2007, profit: 3700},
{year: 2008, profit: 4900},
{year: 2009, profit: 700}]
},
{
name: "SmartPhone",
sales: [{year: 2005, profit: 2000},
{year: 2006, profit: 4000},
{year: 2007, profit: 1810},
{year: 2008, profit: 6540},
{year: 2009, profit: 2820}]
},
{
name: "Software",
sales: [{year: 2005, profit: 1100},
{year: 2006, profit: 1700},
{year: 2007, profit: 1680},
{year: 2008, profit: 4000},
{year: 2009, profit: 4900}]
}
];
var offsetSelect = d3.ui.select({
base: vis,
before: "svg",
style: {position: "absolute", left: width - padding.right + 15 + "px", top: yRangeHeight + "px"},
onchange: function() {
update(dataSet1)
},
data: ["wiggle", "zero", "expand", "silhouette"]
}),
orderSelect = d3.ui.select({
base: vis,
before: "svg",
style: {position: "absolute", left: width - padding.right + 15 + "px", top: yRangeHeight - 20 + "px"},
onchange: function() {
update(dataSet1)
},
data: ["inside-out", "default", "reverse"]
}),
stack = d3.layout.stack()
.values(function(d) { return d.sales; })
.x(function(d) { return d.year; })
.y(function(d) { return d.profit; })
.out(function out(d, y0, y) {
d.p0 = y0;
d.y = y;
}
);
// x Axis
var xPadding = {inner: 0.1, outer: 0.3},
xScale = d3.scale.ordinal()
.rangeBands([0, xRangeWidth], xPadding.inner, xPadding.outer),
xAxis = d3.cbPlot.d3Axis()
.scale(xScale)
.orient("bottom"),
gX = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + yRangeHeight + ")");
// y Axis
var yAxisScale = d3.scale.linear()
.range([yRangeHeight, 0]),
yAxis = d3.cbPlot.d3Axis()
.scale(yAxisScale)
.orient("left")
.tickSubdivide(2),
gY = svg.append("g")
.attr("class", "y axis")
.style({"pointer-events": "none", "font-size": "12px"}),
yAxisTransition = 1000;
var yPlotScale = d3.scale.linear()
.range([0, yRangeHeight]);
var color = d3.scale.category10();
function update(dataSet) {
// create an array of normalised layers and
// add the normalised values onto the data
var normData = stack.offset("expand")(dataSet)
.map(stack.values())
.map(function(s) {
return s.map(function(p) {return p.yNorm = p.y})
}),
stackedData = stack.offset(offsetSelect.value())
.order(orderSelect.value())(dataSet),
maxY = d3.max(stackedData, function(d) {
return d3.max(d.sales, function(s) {
return s.profit + s.p0
})
}),
years = stackedData[0].sales.map(stack.x()),
yearlyTotals = years.reduce(function(t, y) {
return (t[y] = d3.sum(stackedData, function(o) {
return o.sales.filter(function(s) {
return s.year == y
})[0].profit
}), t)
}, {});
xScale.domain(years);
yAxisScale.reset = function(){
this.domain([0, offsetSelect.value() == "expand" ? 1 : maxY])
.range([yRangeHeight, 0])
.ticks(10)
};
yAxisScale.reset();
yPlotScale.domain(yAxisScale.domain());
// plotArea
// (svg) -> (g.plotArea)[stackedData]
// apply a transform to map screen space to cartesian space
// this removes all confusion and mess when plotting data!
var plotArea = svg.selectAll(".plotArea")
.data([stackedData]);
plotArea.enter().insert("g", ".axis")
.attr(d3.cbPlot.transplot(yRangeHeight))
.attr("class", "plotArea");
/*
plotArea.series
(g.plotArea)[stackedData] xF transPlot
?data d
\
`+-> (g.plotArea.series)[stackedData[0]]
:
:
\
`+-> (g.plotArea.series)[stackedData[m]]
*/
plotArea.series = plotArea.selectAll(".series")
.data(ID);
plotArea.series.enter()
.append("g")
.attr("class", "series");
plotArea.series.style("fill", function(d, i) {
return color(i);
});
plotArea.series.exit().remove();
Object.defineProperties(plotArea.series, d3._CB_selection_destructure);
/*
plotArea.series.components
(g.series)[stackedData[0]]
?data d3.entries(d)
\
`+-> (g.name)[stackedData[0].name]
+-> (g.sales)[stackedData[0].sales]
:
:
(g.series)[stackedData[m]]
?data d3.entries(d)
\
`+-> (g.name)[stackedData[m].name]
+-> (g.sales)[stackedData[m].sales]
*/
plotArea.series.components = plotArea.series.selectAll(".components")
.data(function(d) {
return d3.entries(d);
});
plotArea.series.components.enter().append("g")
.attr("class", function(d){return d.key})
.classed("components", true);
plotArea.series.components.exit().remove();
/*
plotArea.series.components.values
(g.series)[stackedData[0]]
\
`+-> (g.sales)[stackedData[0].sales]
:
:
(g.series)[stackedData[m]]
\
`+-> (g.sales)[stackedData[m].sales]
*/
plotArea.series.components.values = plotArea.series.components.filter(function(d){
return d.key == "sales"
});
Object.defineProperties(plotArea.series.components.values, d3._CB_selection_destructure);
/*
plotArea.series.components.labels
(g.series)[stackedData[0]]
\
`+-> (g.name)[stackedData[0].name] xF transPlot
:
:
(g.series)[stackedData[m]]
\
`+-> (g.name)[stackedData[m].name] xF transPlot
*/
plotArea.series.components.labels = plotArea.series.components.filter(function(d){
return d.key == "name"
})
// reverse the plotArea transform (it is it's own inverse)
.attr(d3.cbPlot.transplot(yRangeHeight));
Object.defineProperties(plotArea.series.components.labels, d3._CB_selection_destructure);
var s = xScale.rangeBand(),
w = s - xPadding.inner,
drag = d3.behavior.drag()
.on("dragstart", mouseOver),
/*
plotArea.series.components.values.points
(g.sales)[stackedData[0].sales] * on mouseover; * on mouseout; * bH drag
?data d.value
\
`+-> (rect.point)[stackedData[0].sales.value[0]
:
+-> (rect.point)[stackedData[0].sales.value[n]
:
:
(g.sales)[stackedData[m].sales] * on mouseover; * on mouseout; * bH drag
?data d.value
\
`+-> (rect.point)[stackedData[m].sales.value[0]
:
+-> (rect.point)[stackedData[m].sales.value[n]
*/
points = plotArea.series.components.values.points = plotArea.series.components.values.selectAll("rect")
.data(function(d){
return d.value
});
points.enter()
.append("rect")
.attr({width: w, class: "point"})
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.call(drag);
points.transition()
.attr("x", function(d) {
return xScale(d.year);
})
.attr("y", function(d) {
return yPlotScale(d.p0);
})
.attr("height", function(d) {
return yPlotScale(d.y);
})
.attr("stroke", "white");
points.exit().remove();
Object.defineProperties(plotArea.series.components.values.points, d3._CB_selection_destructure);
gX.transition().call(xAxis);
gY.transition().call(yAxis);
function mouseOver(pointData, pointIndex, groupIndex) {
console.log(["in", pointIndex].join("\t"));
var selectedYear = pointData.year,
// wrap the node in a selection with the proper parent
plotData = plotArea.series.components.values.data,
seriesData = plotData[groupIndex],
currentYear = d3.transpose(plotData)[pointIndex],
point = plotArea.series.components.values.points.nodes[groupIndex][pointIndex];
// if the plot is not normalised, fly-in the axis on the selected year
if(offsetSelect.value() != "expand") {
yAxisScale.reset();
// get the zero offset for the fly-in axis
var pMin = d3.min(currentYear, function(s) {
return s.p0
}),
refP0 = seriesData[pointIndex].p0,
selectedGroupHeight = d3.sum(currentYear, function(d) {return d.y}),
// set the range and domain height for the selected year
localDomain = [0, selectedGroupHeight].map(function(d){return d + pMin - refP0}),
localRange = [0, selectedGroupHeight].map(function(d) {return yAxisScale(d + pMin)});
console.log(yAxisScale(pMin));
yAxisScale
.domain(localDomain)
.range(localRange);
// apply the changes to the y axis and manage the ticks
gY.transition("axis")
.duration(yAxisTransition)
.call(yAxis.ticks(+(Math.abs(localRange[0] - localRange[1]) / 15).toFixed()))
.attr("transform", "translate(" + point.attr("x") + ",0)")
.style({"font-size": "8px"})
.call(function(t) {d3.select(t.node()).classed("fly-in", true)});
// align the selected series across all years
points.transition("points")
.attr("y", alignY(seriesData[pointIndex].p0, groupIndex))
.call(endAll, toolTip)
} else window.setTimeout(toolTip, 0); // if not expand
// manage the highlighting
// points highlighting
plotArea.series.transition("fade")
.attr("opacity", function(d, i) {
return i == groupIndex ? 1 : 0.5;
});
// x axis highlighting
d3.selectAll(".x.axis .tick")
.filter(function(d) {
return d == selectedYear
})
.classed("highlight", true);
// move the selected element to the front
d3.select(this.parentNode)
.moveToFront();
gX.moveToFront();
legendText(groupIndex);
// Tooltip
function toolTip() {
plotArea.series
.append("g")
.attr("class", "tooltip")
.attr("transform", "translate(" + [point.attr("x"), point.attr("y")] + ")")
.append("text")
.attr(d3.cbPlot.transflip())
.text(d3.format(">8.0%")(pointData.yNorm))
.attr({x: "1em", y: -point.attr("height") / 2, dy: ".35em", opacity: 0})
.transition("tooltip").attr("opacity", 1)
.style({fill: "black", "pointer-events": "none"})
}
}
function mouseOut(d, nodeIndex, groupIndex) {
console.log(["out", nodeIndex].join("\t"));
var year = d.year;
d3.selectAll(".x.axis .tick")
.filter(function(d) {
return d == year
})
.classed("highlight", false);
plotArea.series.transition("fade")
.attr({opacity: 1});
var g = plotArea.series.components.labels.nodes[groupIndex][0].select("text");
g.classed("highlight", false);
g.text(g.text().split(":")[0])
yAxisScale.reset();
gY.selectAll(".minor").remove();
gY.transition("axis").call(yAxis)
.attr("transform", "translate(0,0)")
.style({"font-size": "12px"})
.call(function(t) {d3.select(t.node()).classed("fly-in", false)});
plotArea.series.selectAll(".tooltip")
.transition("tooltip")
.attr({opacity: 0})
.remove();
points.transition("points").attr("y", function(d) {
return yPlotScale(d.p0);
})
};
/*
plotArea.series.components.labels
(g.name)[stackedData[0].name]
?data
\
`+-> (g.label)[stackedData[0].name.value xF transPlot
:
:
(g.name)[stackedData[m].name]
?data
\
`+-> (g.label)[stackedData[m].name.value xF transPlot
*/
// Add the legend inside the series containers
// The series legend is wrapped in another g so that the
// plot transform can be reversed. Otherwise the text would be mirrored
var labHeight = 40,
labRadius = 10;
/*
plotArea.series.components.labels.circles
(g.name)[stackedData[0].name] xF transPlot
?data [d.value]
\
`+-> (circle)[stackedData[0].name.value]
:
:
(g.name)[stackedData[m].name] xF transPlot
?data [d.value]
\
`+-> (circle)[stackedData[0].name.value]
*/
// add the marker and the legend text to the normalised container
// push the stackedData (name) down to them
var labelCircle = plotArea.series.components.labels.selectAll("circle")
.data(function(d){return [d.value]}),
// take a moment to get the series order delivered by stack
orders = stackedData.map(function(d) { // simplify the form
return {name: d.name, base: d.sales[0].p0}
}).sort(function(a, b) { // get a copy, sorted by p0
return a.base - b.base
}).map(function(d) { // convert to index permutations
return stackedData.map(function(p) {
return p.name
}).indexOf(d.name)
}).reverse(); // convert to screen y ordinate
labelCircle.enter().append("circle")
.on("mouseover", function(pointData, pointIndex, groupIndex) {
var node = this,
typicalP0 = d3.median(plotArea.series.components.values.data[groupIndex],
function(d){return d.p0});
plotArea.series.components.values.points.transition("points")
.attr("y", alignY(typicalP0, groupIndex));
plotArea.series.transition("fade")
.attr("opacity", function(d) {
return d === d3.select(node.parentNode.parentNode).datum() ? 1 : 0.5;
});
legendText(groupIndex);
})
.on("mouseout", function(pointData, pointIndex, groupIndex) {
plotArea.series.transition("fade")
.attr({opacity: 1});
plotArea.series.components.values.points.transition("points").attr("y", function(d) {
return yPlotScale(d.p0);
})
});
labelCircle.attr("cx", xRangeWidth + 20)
.attr("cy", function(d, i, j) {
return labHeight * orders[j];
})
.attr("r", labRadius);
/*
plotArea.series.components.labels.text
(g.name)[stackedData[0].name] xF transPlot
?data [d.value]
\
`+-> (text)[stackedData[0].name.value]
:
:
(g.name)[stackedData[m].name] xF transPlot
?data [d.value]
\
`+-> (text)[stackedData[0].name.value]
*/
var labelText = plotArea.series.components.labels.selectAll("text")
.data(function(d){return [d.value]});
labelText.enter().append("text");
labelText.attr("x", xRangeWidth + 40)
.attr("y", function(d, i, j) {
return labHeight * orders[j];
})
.attr("dy", labRadius / 2)
.text(function(d) {
return d;
});
function legendText(groupIndex){
// Legend text
// add the value for the moused over item to the legend text and
// highlight it
var labelText = plotArea.series.components.labels.nodes[groupIndex][0].select("text"),
seriesData = plotArea.series.components.values.data[groupIndex],
fmt = [">8,.0f", ">8.0%"][(offsetSelect.value() == "expand") * 1];
labelText.classed("highlight", true);
labelText.text(labelText.datum().value + ": " + d3.format(fmt)(
offsetSelect.value() != "expand" ?
d3.sum(seriesData, stack.y()) :
d3.sum(seriesData, function(s) {
var totalSales = d3.sum(d3.values(yearlyTotals));
return s.y * yearlyTotals[s.year] / totalSales
})
));
}
function alignY(p0, series) {
var offsets = plotArea.series.components.values.data[series].map(function(d) {
return p0 - d.p0;
});
return function(d, i) {
return yPlotScale(d.p0 + offsets[i]);
}
}
function aID(d) {
return [d];
}
function ID(d) {
return d;
}
}
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
d3._CB_selection_destructure = {
"nodes": {
get: function() {
return this.map(function(g) {
return g.map(function(n) {
return d3.select(n)
})
})
}
},
data: {
get: function() {
return this.map(function(g) {
return d3.select(g[0]).datum().value
})
}
}
};
update(dataSet1);
window.setTimeout(function(){
update(dataSet1.map(function(d) {
return {
name: d.name, sales: d.sales.map(function(y) {
return {year: y.year, profit: y.profit / 2}
})
}
})
)
},1000)
PK!b�h^�[Content_Types].xml �(����N�0E�H�C�-Jܲ@5��*Q>�ēƪc[�ii����B�j7���{2��h�nm���ƻR ����U^7/���%��rZY�@1__�f��q��R4D�AJ�h>����V�ƹ �Z�9����NV�8ʩ����ji){^��-I�"{�v^�P!XS)bR�r��K�s(�3�`c�0��������7M4�����ZƐk+�|\|z�(���P��6h_-[�@�!���Pk���2n�}�?�L��� ��%���d����dN"m,�ǞDO97*�~��ɸ8�O�c|n���E������B��!$}�����;{���[����2���PK!�U0#�L _rels/.rels �(���MO�0 ��H�����ݐBKwAH�!T~�I����$ݿ'T�G�~����<���!��4��;#�w����qu*&r�Fq���v�����GJy(v��*����K��#F��D��.W ��=��Z�MY�b���BS�����7��ϛז�� ?�9L�ҙ�sbgٮ|�l!��USh9i�b�r:"y_dl��D���|-N��R"4�2�G�%��Z�4�˝y�7 ë��ɂ�����PK!�>����xl/_rels/workbook.xml.rels �(��RMK�0� ��0w�v�t/"�U�ɴ)�&!3~��*�]X�K/o�y���v�5���+��zl�;o���b���G���� �s�>��,�8��(%���"D��҆4j�0u2js��MY�˴���S쭂��� �)f���C����y�� I< y���!+��E���fMy�k��� ��K�5=|�t ��G)�s墙�U��tB��)���,���f�������PK! �V
h�xl/workbook.xml���n�0��H���}��$tMR��ATBP�i����83��a�B�;�I�n�`ߒ����9Y��@_��\��GFLQ]s�-���m0��:�j"�b~`���_-���6Z��-p�\����-���)8i�����lC�Fj�2��(zJ� �y C7 ��B�^2�F�a�8߶��Mҗ�$1�� �� 6\p�0@1�4��*m�F@ڇ8��0}���mu��A>�7��8S. �n����@��E`$�u��;V� ,����a���N�4M"�?��2�c#k�r{��F5kH/�̙�@6K�� �3�Qı�V�}t�_u��V�k�}�aP,^�rOBs��Wĵ�7��˼�� KuVK���h[�q��n\_s��(�UWFf�٪�U7�O=�ˠ�ЖWA��]��9�,��;B�X!�5f4��T�\����loy��p�U���.{x2����vm���<��q���ցU��$.�Z �F�����C����W F&�01�u<��(ԗ ËY������[W.`Wx���itv��A��eA:?I�y:K�ez�������<���#��1>J��ڀ��g�f�9��0cB'81EN_�?��PK!N�P���xl/sharedStrings.xmld��N�0 �;��!�ubi%�a�@�Qk�H�SjwloO�.�9����`{��A�q�T�A�k@!u�Otr���v� �%P�B��� ���̢�.��(2�h�]�xWF�e�U�d��I�8a�9"J�i���!���$���fJ�3o�>�����n�x��� ��B��djz���ESMU|]_�Yö��������a� ��PK!��nX��xl/theme/theme1.xml�Yϋ7��? sw�kfl/�{lg��&!�䨵e����ɻ1!P�c�P��^
��P��%�k�MiSȿ�'��#��n�n -Y�2�����7ߓ4/ݍ�s�SNX�v�*����d�vo���p��1�,�mw��{i���.�-�;�?�[��FB̶�e>�f�/�N�ل�1p�N���ݘ�k�JP�I\'A1��6��v�Ҥ��4ާp�.F4ݗ���CaLJU�� ��9B���8cv<�w��P�<h��疷/��Vމ� }�~����;�kj�tz���|/��+�~��=@��4�����n�ϱ(����5z�������8w|�3�
������A^4�
��}�O��3�
��5|���y �@%�����p9�d����A��/P� ��CLX"6�Z��t �H�����dq�(9H��K�$� %�Cs�VT��_�<u�<��0�zK^���5I>�d&��`�� /�}������'��<����Ó?f���;(��_|�ٟ_�������㹎���O~��s;&[x����{���W����# ���>$1��U|��`1�My�d���c!b�@ض���^] j�u��[)� xy~���sA,#_�b������+r,���y2���u� ��lc�(1B۟�@Y��da��u���8�‘��!Ɩ��&�������pn����%Cr`$R�i����� �����-�˨m�=|d"�@�B~�����h.Pl39D1���Dd#��HG:��Dz�)s�c̹�ϵ��
��=�{t��T�C��]Ę���0B��ʙ$����B�"�:6�3�yq@��p�"���BptU�T$�|2O-�����>.�a�2 ����$9S�O���NԳ�tZ�;)��Z;��|�?(�=4O�cxg� �;�~����^�7���څP���u�v�7.�'��}��x���;��4@��V���j+7��2�(�i�T'e�#"���`�_U�)�MO�3cV��Ym��)�j�0���8۱V�rw��G�h���v�m� 4�]�ʼ��N�nyI@��'$��Lu �Ʋ��w$��΅E�¢)�/C����@mX?9��j������
Q<�q��ѕ�9�Hor&�3� ("ݒ\7NO�.K�W��ABK7�����<;����u��AO�b�64�7k)"���&�R��9n�A݇ӱ��� ���2�A�p��Et
�g#�f/��(�,墇x�9\�N�18u(�ۮ��*h�4Dq��@�Zr-����� 2�L�H�a�Z���[P�L+�OU��˞l�ޏ�����7��ߨJ� ��j��1�͕��w�0岫)����E(�(��gp%�+:�n��.�38t݅SY`�u�=�TK�i�Y�LCUdմ��+�����2�V�^h]k�u���*qF�}���Q+3�I��2,5;o5���@�D��o�a���V~�w:ke�X�+U�O�� vpģ��s*�
%|{H,����L6��+�5"\9��{��5?,U�~��սJ��wꥎ�׫}�Z�uk����(���g��G�E��E��}���GnF,.3������0���0ѹ��z��Z�Π����R+ ��^6z�^�7[���s��^�zA�Y
�aX򂊤�l�^����f���ϗ10�L>r_�{�����PK!^@�4 xl/styles.xml�U�o�0~�����;��%P5M�*uS�v�^0Ī0�td���� I�ZmY�0^�����}>�����)���,���\LV �|�y3�CdA��4�;�������1;N�6��I�Ƙz��M���4g��fJ�1`��ojMI��M��� ���0�;���OD?lk/W�&��gf�0�⺒J�5�m8!����˵jTi��We�r���ܟ���ƥ��A��J�h{��A�o2�S�٭J��;z$<!��8W\id 3@�y$�[qI8[kf��D0���#�p��� �Y�oy��61��Ɩ8��c�����j8^B!;��/�+Mv�(:}C�8+,�����u�3���Y�L��E���> l�s��b\+]�H��߻Ҙ���f��������h�TJn�ց�S�﬐��O��ɭȄ�zp%l��C��;�ΰ�C�{;���;,j��v#R�|w�Y%�ě� ��D��w��5�y�ɬ-�D}�*Co5-Yۋ��F �zH@�U��XP���~c���j�����@h5?��A�ȶ�� �C�ꕅ�[� �/�0��x3��5���;s8�\Вl��?L&�8�H �P�~�-{T�A$�8��8��V@[s�@��?�j��W���U6�f�r�M�4���r�E���j�̓Qp�s�����݋�'���{��`{�wG_�FG�52�=�>M��( �l��dJf�l:��,
G��dyeр{�:�a��a��Z���0A9��Z�+4�B���C����O��PK!��%���xl/worksheets/sheet1.xml�Y�r�8�O�����`lCp f�{Cd�l���)�y����R˒�lB�>���QK�������I�4�#��tM��x�F�O�ŵi������@F�I�_��~��{�#$3��!��,;,+ v$��N|$�l�$�3xLެ��Ô���t��V�3�0Hαo�a@���#"�,7�������.<���(8�\�'�Nj ��`b���5�(��q�������6{��Ga��i��:`���c��n,�4�<,�M�~s�h:m�7!DGKb$d;2o���{EL�9$_)�����ٓ #��i�ڬ���g0ԥ����ڀ ��?��2����o� ��!V�`�sG�r f:N�Z
�=L�(Ҹ�+���~��l72{��ӿ��n�I�y!5i�G���Kb�VFz��-�8g��,́�J��9/Л��o1�۱{�K��ݛB ~��6��MN�)͜�� ʵ��sf� wa����sݷ��ح��Ew~揇I�e����ӣO[�;p�l=o�0|Kь @����;�>��A��Ɉ�
a����0W��)�N\3�].��VN9�S�R��pE�'#z"b*#�(t���s$��i�i�⾇�� �o�N��h��ɈK�2q�R&#�Q�JDMY�2>eH�O���U�O
��l�8�.y�S��в�4����^&@���W0E��#_�r�n=�F�z�x=���&���х�f#�B�E������X��f��<�sᩍ���X��k� ����'(Z� d�2w��Yf%���Fj�#f/r_<-��F/�%�$�J�����F��9�gJ�K�Ь����`�I�%\��!�t:�Tgv��+a��id!�<J#Kid%�<�#B��G��8gp�ē��?��0���RYI#O���3P�M�)�a���W��v�����*ᩉ��A��g�}a��������C4{��sP<��jf�]�0� �8P*_���
�� j��`��5m|�z��� ���@�f͐y�O�h��"0A,����ʙ���f�V5�%nh��\��@��vZ� ������+��>93[��;O'�
B� �s��nu��ؚú'QC�*��3p R�6|���Ħhe�x!WCvΝV���G6�z���T�1S
��PA9��~�|�t]bF'O�
�3�P(��o�����fJU{A(վ��՞�c�~���Asy��40�͠b� �K^!��/�f�}�C@��8�~ %6i�f�f��b4mzƭ�V� *� �IU�jHUo] �M��u�;p�� ��2Ʃ ���k*:�۪CQ��Z̖�Q BL-�pV ����W���̈�S B�T�TM*-D��ۦkMZ鵦wMM�!�Pt���mlO�pʼn/��Tu_� B�C��ٙjR1�Vm�-�!Wkt)6�05<��L���˅�1�V�4���n�:�TJ\���~�/̿��7r�'o�!5�d˾�n�,�v���+(�:��3_���o�nF���6���~���"����PK!���bC]docProps/core.xml �(���_K�0���C�{�f�RBہʞ�V�br��?$�n�޴�je>y�='�{�%�r/�� ���*IR��Z�j[��jg(r�)���@phY^^��P�-<Xm��\H�Qn
���P�߁d. č���p�[l�`[��4��<�3�c3�)��4����ch@������+ݟze┵?�0�1�-� �Gc۶I;�c������Q�Zu���\p�-0�m���Êr<)u�k����M ��0�Ε��@QC��'�e~{W�P9K�U�f�TiF)]\�u���� yl�bV�9]�3%�e��>D� ��PK!�AيdocProps/app.xml �(���Ao�0 ��� �9�: ��bH7��b�vgM�c��$����׏���i{ڍ�{x�DI]:_����P��k�������*
$j�c�Jŵ��AmrL���-QZI��������Jsg�ۼ��i���h�:$/��A���H�@1%�z���:ځv���Z}K�;k�o���cC�����\TL���u��U[k<�9X7�#(�2P�`��m�˨UO�,�\���k���0�T�7ٙ@�5ئf�}B��w̏�*Ɇi8�s�v��r4pqn&�w�<�fc2�C��� ��3�|���W���%�,���.<�}��C���ڶ&C�/pZ�i�ny��!�ք=�Ϟ������˫E���w�͔|�����PK-!b�h^�[Content_Types].xmlPK-!�U0#�L �_rels/.relsPK-!�>�����xl/_rels/workbook.xml.relsPK-! �V
h��xl/workbook.xmlPK-!N�P���� xl/sharedStrings.xmlPK-!��nX��~ xl/theme/theme1.xmlPK-!^@�4 Bxl/styles.xmlPK-!��%���txl/worksheets/sheet1.xmlPK-!���bC]�docProps/core.xmlPK-!�AيdocProps/app.xmlPK
��!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment