Skip to content

Commit 61d36e0

Browse files
Emmanuel Schanzerjpolitz
authored andcommitted
fix pie chart handling where the legend paginates
1 parent 8c96477 commit 61d36e0

File tree

1 file changed

+68
-46
lines changed

1 file changed

+68
-46
lines changed

src/web/js/trove/chart-lib.js

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -570,47 +570,54 @@
570570
overlay: (overlay, restarter, chart, container) => {
571571
// If we don't have images, our work is done!
572572
if(!hasImage) { return; }
573-
574-
// if custom images are defined, use the image at that location
575-
// and overlay it atop each dot
576-
google.visualization.events.addListener(chart, 'ready', function () {
577-
// HACK(Emmanuel):
578-
// The only way to hijack marker events is to walk the DOM here
579-
// If Google changes the DOM, these lines will likely break
580-
const svgRoot = chart.container.querySelector('svg');
573+
const svgRoot = chart.container.querySelector('svg');
581574

582-
// The order of SVG slices is *not* the order of the rows in the table!!
583-
// - 1 or 2 slices: drawn in reverse order
584-
// - More than 2 slices: the first row in the table is the first SVG
585-
// slice, but the rest are in reverse order
586-
let slices;
587-
if(table.length <= 2) {
588-
slices = Array.prototype.slice.call(svgRoot.children, 2, -1).reverse();
589-
} else {
590-
slices = Array.prototype.slice.call(svgRoot.children, 3, -1).reverse();
591-
slices.unshift(svgRoot.children[2]);
592-
}
593-
const defs = svgRoot.children[0];
594-
const legendImgs = svgRoot.children[1].querySelectorAll('g[column-id]');
575+
function customDraw() {
595576

596577
// remove any labels that have previously been drawn
597578
$('.__img_labels').each((idx, n) => $(n).remove());
598579

599-
// Render each slice under the old ones, using the image as a pattern
600-
table.forEach((row, i) => {
601-
const oldDot = legendImgs[i].querySelector('circle');
602-
const oldSlice = slices[i];
603-
604-
// render the image to an img tag
580+
// HACK(Emmanuel):
581+
// The only way to hijack marker events is to walk the DOM here
582+
// If Google changes the DOM, these lines will likely break
583+
const svgRoot = chart.container.querySelector('svg');
584+
const defs = svgRoot.children[0]; // invisible defs group
585+
const slices = [...svgRoot.children].slice(2, -1); // skip defs, legend & empty last elt
586+
587+
// As we walk through the rows, keep track of what % of the total
588+
// we've seen AND how many slices we've walked clockwise
589+
const total = table.reduce((acc, row) => row[1]+acc, 0);
590+
let pctProcessed = 0;
591+
let clockwiseSlices = 0;
592+
593+
// Walk through the table row by row, and the pie clockwise from 12pm
594+
// For each row, find the DOM node for the corresponding slice
595+
// Render a new, decorated slice under the old one using the image as a pattern
596+
table.forEach((row, i) => {
597+
pctProcessed += (row[1] / total); // update how much of the pie we've walked
598+
599+
// Find the associated DOM node for the slice
600+
// if we've walked <50%, the nodes are in CW order
601+
// if we've walked >=50%, the node CCW. Jump to the last slice and count backwards
602+
let oldSlice;
603+
if(pctProcessed < .50) {
604+
oldSlice = slices[i];
605+
clockwiseSlices++;
606+
} else {
607+
oldSlice = slices[(slices.length-1) - (i - clockwiseSlices)];
608+
}
609+
610+
// Render the image, and convert to a dataURL.
605611
const imgDOM = row[3].val.toDomNode();
606612
row[3].val.render(imgDOM.getContext('2d'), 0, 0);
607-
608-
// make an SVGimage element from the img tag, and make it the size of the slice
609-
const sliceBox = oldSlice.getBoundingClientRect();
613+
const imgURL = imgDOM.toDataURL()
614+
615+
// Make an SVGimage element (same size as slice) from the imgURL
616+
const {width, height} = oldSlice.getBoundingClientRect();
610617
const imageElt = document.createElementNS("http://www.w3.org/2000/svg", 'image');
618+
imageElt.setAttributeNS(null, 'href', imgURL);
619+
imageElt.setAttribute('width', Math.max(width, height));
611620
imageElt.classList.add('__img_labels'); // tag for later garbage collection
612-
imageElt.setAttributeNS(null, 'href', imgDOM.toDataURL());
613-
imageElt.setAttribute('width', Math.max(sliceBox.width, sliceBox.height));
614621

615622
// create a pattern from that image
616623
const patternElt = document.createElementNS("http://www.w3.org/2000/svg", 'pattern');
@@ -619,30 +626,45 @@
619626
patternElt.setAttribute('width', 1);
620627
patternElt.setAttribute('height', 1);
621628
patternElt.setAttribute( 'id', 'pic'+i);
629+
patternElt.classList.add('__img_labels'); // tag for later garbage collection
630+
631+
// and add it to the (invisible) defs element
632+
patternElt.appendChild(imageElt);
633+
defs.append(patternElt);
622634

623635
// make a new slice, copy elements from the old slice, and fill with the pattern
624636
const newSlice = document.createElementNS("http://www.w3.org/2000/svg", 'path');
625637
Object.assign(newSlice, oldSlice); // we should probably not steal *everything*...
626638
newSlice.setAttribute( 'd', oldSlice.firstChild.getAttribute('d'));
627639
newSlice.setAttribute( 'fill', 'url(#pic'+i+')');
628-
629-
// add the image to the pattern and the pattern to the defs
630-
patternElt.appendChild(imageElt);
631-
defs.append(patternElt);
640+
newSlice.classList.add('__img_labels'); // tag for later garbage collection
632641

633642
// insert the new slice before the now-transparent old slice
634643
oldSlice.parentNode.insertBefore(newSlice, oldSlice)
635-
636-
// make a new dot, then set size and position of dot to replace the old dot
637-
const newDot = imageElt.cloneNode(true);
638-
const radius = oldDot.r.animVal.value;
639-
newDot.setAttribute('x', oldDot.cx.animVal.value - radius);
640-
newDot.setAttribute('y', oldDot.cy.animVal.value - radius);
641-
newDot.setAttribute('width', radius * 2);
642-
newDot.setAttribute('height', radius * 2);
643-
oldDot.parentNode.replaceChild(newDot, oldDot);
644644
});
645-
});
645+
646+
// After 100ms, check if each row is represented in the legend, and needs redrawing
647+
const delayedDrawLegend = () => setTimeout(() => {
648+
console.log('drawing legend');
649+
const legend = svgRoot.children[1]; // legend group
650+
const legendEntries = [...legend.querySelectorAll('g[column-id]')];
651+
table.forEach((row, i) => {
652+
const entry = legendEntries.find(e => e.getAttribute('column-id') == row[0]);
653+
if(entry) {
654+
const oldDot = entry.querySelector('circle');
655+
oldDot.setAttribute( 'fill', 'url(#pic'+i+')');
656+
}
657+
})
658+
legend.addEventListener("click", delayedDrawLegend)
659+
}, 100);
660+
661+
// force the legend to draw, the first time around
662+
delayedDrawLegend();
663+
}
664+
665+
// if custom images are defined, use the image at that location
666+
// and overlay it atop each dot
667+
google.visualization.events.addListener(chart, 'ready', customDraw);
646668
}
647669
}
648670
}

0 commit comments

Comments
 (0)