const initLegend = function () { const $$ = this; $$.legendItemTextBox = {}; $$.legendHasRendered = false; $$.legend = $$.svg.append('g').attr('transform', $$.getTranslate('legend')); if (!$$.config.legend_show) { $$.legend.style('visibility', 'hidden'); $$.hiddenLegendIds = $$.mapToIds($$.data.targets); return; } // MEMO: call here to update legend box and tranlate for all // MEMO: translate will be upated by this, so transform not needed in updateLegend() $$.updateLegendWithDefaults(); }; const updateLegendWithDefaults = function () { const $$ = this; $$.updateLegend($$.mapToIds($$.data.targets), { withTransform: false, withTransitionForTransform: false, withTransition: false }); }; const updateSizeForLegend = function (legendHeight, legendWidth) { let $$ = this, config = $$.config, insetLegendPosition = { top: $$.isLegendTop ? $$.getCurrentPaddingTop() + config.legend_inset_y + 5.5 : $$.currentHeight - legendHeight - $$.getCurrentPaddingBottom() - config.legend_inset_y, left: $$.isLegendLeft ? $$.getCurrentPaddingLeft() + config.legend_inset_x + 0.5 : $$.currentWidth - legendWidth - $$.getCurrentPaddingRight() - config.legend_inset_x + 0.5, }; $$.margin3 = { top: $$.isLegendRight ? 0 : $$.isLegendInset ? insetLegendPosition.top : $$.currentHeight - legendHeight, right: NaN, bottom: 0, left: $$.isLegendRight ? $$.currentWidth - legendWidth : $$.isLegendInset ? insetLegendPosition.left : 0, }; }; const transformLegend = function (withTransition) { const $$ = this; (withTransition ? $$.legend.transition() : $$.legend).attr('transform', $$.getTranslate('legend')); }; const updateLegendStep = function (step) { this.legendStep = step; }; const updateLegendItemWidth = function (w) { this.legendItemWidth = w; }; const updateLegendItemHeight = function (h) { this.legendItemHeight = h; }; const getLegendWidth = function () { const $$ = this; return $$.config.legend_show ? $$.isLegendRight || $$.isLegendInset ? $$.legendItemWidth * ($$.legendStep + 1) : $$.currentWidth : 0; }; const getLegendHeight = function () { let $$ = this, h = 0; if ($$.config.legend_show) { if ($$.isLegendRight) { h = $$.currentHeight; } else { h = Math.max(20, $$.legendItemHeight) * ($$.legendStep + 1); } } return h; }; const opacityForLegend = function (legendItem) { return legendItem.classed(CLASS.legendItemHidden) ? null : 1; }; const opacityForUnfocusedLegend = function (legendItem) { return legendItem.classed(CLASS.legendItemHidden) ? null : 0.3; }; const toggleFocusLegend = function (targetIds, focus) { const $$ = this; targetIds = $$.mapToTargetIds(targetIds); $$.legend.selectAll('.' + CLASS.legendItem) .filter((id) => { return targetIds.indexOf(id) >= 0; }) .classed(CLASS.legendItemFocused, focus) .transition().duration(100) .style('opacity', function () { const opacity = focus ? $$.opacityForLegend : $$.opacityForUnfocusedLegend; return opacity.call($$, $$.d3.select(this)); }); }; const revertLegend = function () { let $$ = this, d3 = $$.d3; $$.legend.selectAll('.' + CLASS.legendItem) .classed(CLASS.legendItemFocused, false) .transition().duration(100) .style('opacity', function () { return $$.opacityForLegend(d3.select(this)); }); }; const showLegend = function (targetIds) { let $$ = this, config = $$.config; if (!config.legend_show) { config.legend_show = true; $$.legend.style('visibility', 'visible'); if (!$$.legendHasRendered) { $$.updateLegendWithDefaults(); } } $$.removeHiddenLegendIds(targetIds); $$.legend.selectAll($$.selectorLegends(targetIds)) .style('visibility', 'visible') .transition() .style('opacity', function () { return $$.opacityForLegend($$.d3.select(this)); }); }; const hideLegend = function (targetIds) { let $$ = this, config = $$.config; if (config.legend_show && isEmpty(targetIds)) { config.legend_show = false; $$.legend.style('visibility', 'hidden'); } $$.addHiddenLegendIds(targetIds); $$.legend.selectAll($$.selectorLegends(targetIds)) .style('opacity', 0) .style('visibility', 'hidden'); }; const clearLegendItemTextBoxCache = function () { this.legendItemTextBox = {}; }; const updateLegend = function (targetIds, options, transitions) { let $$ = this, config = $$.config; let xForLegend, xForLegendText, xForLegendRect, yForLegend, yForLegendText, yForLegendRect, x1ForLegendTile, x2ForLegendTile, yForLegendTile; let paddingTop = 4, paddingRight = 10, maxWidth = 0, maxHeight = 0, posMin = 10, tileWidth = config.legend_item_tile_width + 5; let l, totalLength = 0, offsets = {}, widths = {}, heights = {}, margins = [0], steps = {}, step = 0; let withTransition, withTransitionForTransform; let texts, rects, tiles, background; // Skip elements when their name is set to null targetIds = targetIds.filter((id) => { return !isDefined(config.data_names[id]) || config.data_names[id] !== null; }); options = options || {}; withTransition = getOption(options, 'withTransition', true); withTransitionForTransform = getOption(options, 'withTransitionForTransform', true); function getTextBox(textElement, id) { if (!$$.legendItemTextBox[id]) { $$.legendItemTextBox[id] = $$.getTextRect(textElement.textContent, CLASS.legendItem, textElement); } return $$.legendItemTextBox[id]; } function updatePositions(textElement, id, index) { let reset = index === 0, isLast = index === targetIds.length - 1, box = getTextBox(textElement, id), itemWidth = box.width + tileWidth + (isLast && !($$.isLegendRight || $$.isLegendInset) ? 0 : paddingRight) + config.legend_padding, itemHeight = box.height + paddingTop, itemLength = $$.isLegendRight || $$.isLegendInset ? itemHeight : itemWidth, areaLength = $$.isLegendRight || $$.isLegendInset ? $$.getLegendHeight() : $$.getLegendWidth(), margin, maxLength; // MEMO: care about condifion of step, totalLength function updateValues(id, withoutStep) { if (!withoutStep) { margin = (areaLength - totalLength - itemLength) / 2; if (margin < posMin) { margin = (areaLength - itemLength) / 2; totalLength = 0; step++; } } steps[id] = step; margins[step] = $$.isLegendInset ? 10 : margin; offsets[id] = totalLength; totalLength += itemLength; } if (reset) { totalLength = 0; step = 0; maxWidth = 0; maxHeight = 0; } if (config.legend_show && !$$.isLegendToShow(id)) { widths[id] = heights[id] = steps[id] = offsets[id] = 0; return; } widths[id] = itemWidth; heights[id] = itemHeight; if (!maxWidth || itemWidth >= maxWidth) { maxWidth = itemWidth; } if (!maxHeight || itemHeight >= maxHeight) { maxHeight = itemHeight; } maxLength = $$.isLegendRight || $$.isLegendInset ? maxHeight : maxWidth; if (config.legend_equally) { Object.keys(widths).forEach((id) => { widths[id] = maxWidth; }); Object.keys(heights).forEach((id) => { heights[id] = maxHeight; }); margin = (areaLength - maxLength * targetIds.length) / 2; if (margin < posMin) { totalLength = 0; step = 0; targetIds.forEach((id) => { updateValues(id); }); } else { updateValues(id, true); } } else { updateValues(id); } } if ($$.isLegendInset) { step = config.legend_inset_step ? config.legend_inset_step : targetIds.length; $$.updateLegendStep(step); } if ($$.isLegendRight) { xForLegend = function (id) { return maxWidth * steps[id]; }; yForLegend = function (id) { return margins[steps[id]] + offsets[id]; }; } else if ($$.isLegendInset) { xForLegend = function (id) { return maxWidth * steps[id] + 10; }; yForLegend = function (id) { return margins[steps[id]] + offsets[id]; }; } else { xForLegend = function (id) { return margins[steps[id]] + offsets[id]; }; yForLegend = function (id) { return maxHeight * steps[id]; }; } xForLegendText = function (id, i) { return xForLegend(id, i) + 4 + config.legend_item_tile_width; }; yForLegendText = function (id, i) { return yForLegend(id, i) + 9; }; xForLegendRect = function (id, i) { return xForLegend(id, i); }; yForLegendRect = function (id, i) { return yForLegend(id, i) - 5; }; x1ForLegendTile = function (id, i) { return xForLegend(id, i) - 2; }; x2ForLegendTile = function (id, i) { return xForLegend(id, i) - 2 + config.legend_item_tile_width; }; yForLegendTile = function (id, i) { return yForLegend(id, i) + 4; }; // Define g for legend area l = $$.legend.selectAll('.' + CLASS.legendItem) .data(targetIds) .enter().append('g') .attr('class', (id) => { return $$.generateClass(CLASS.legendItem, id); }) .style('visibility', (id) => { return $$.isLegendToShow(id) ? 'visible' : 'hidden'; }) .style('cursor', 'pointer') .on('click', (id) => { if (config.legend_item_onclick) { config.legend_item_onclick.call($$, id); } else { if ($$.d3.event.altKey) { $$.api.hide(); $$.api.show(id); } else { $$.api.toggle(id); $$.isTargetToShow(id) ? $$.api.focus(id) : $$.api.revert(); } } }) .on('mouseover', function (id) { if (config.legend_item_onmouseover) { config.legend_item_onmouseover.call($$, id); } else { $$.d3.select(this).classed(CLASS.legendItemFocused, true); if (!$$.transiting && $$.isTargetToShow(id)) { $$.api.focus(id); } } }) .on('mouseout', function (id) { if (config.legend_item_onmouseout) { config.legend_item_onmouseout.call($$, id); } else { $$.d3.select(this).classed(CLASS.legendItemFocused, false); $$.api.revert(); } }); l.append('text') .text((id) => { return isDefined(config.data_names[id]) ? config.data_names[id] : id; }) .each(function (id, i) { updatePositions(this, id, i); }) .style('pointer-events', 'none') .attr('x', $$.isLegendRight || $$.isLegendInset ? xForLegendText : -200) .attr('y', $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendText); l.append('rect') .attr('class', CLASS.legendItemEvent) .style('fill-opacity', 0) .attr('x', $$.isLegendRight || $$.isLegendInset ? xForLegendRect : -200) .attr('y', $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendRect); l.append('line') .attr('class', CLASS.legendItemTile) .style('stroke', $$.color) .style('pointer-events', 'none') .attr('x1', $$.isLegendRight || $$.isLegendInset ? x1ForLegendTile : -200) .attr('y1', $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) .attr('x2', $$.isLegendRight || $$.isLegendInset ? x2ForLegendTile : -200) .attr('y2', $$.isLegendRight || $$.isLegendInset ? -200 : yForLegendTile) .attr('stroke-width', config.legend_item_tile_height); // Set background for inset legend background = $$.legend.select('.' + CLASS.legendBackground + ' rect'); if ($$.isLegendInset && maxWidth > 0 && background.size() === 0) { background = $$.legend.insert('g', '.' + CLASS.legendItem) .attr('class', CLASS.legendBackground) .append('rect'); } texts = $$.legend.selectAll('text') .data(targetIds) .text((id) => { return isDefined(config.data_names[id]) ? config.data_names[id] : id; }) // MEMO: needed for update .each(function (id, i) { updatePositions(this, id, i); }); (withTransition ? texts.transition() : texts) .attr('x', xForLegendText) .attr('y', yForLegendText); rects = $$.legend.selectAll('rect.' + CLASS.legendItemEvent) .data(targetIds); (withTransition ? rects.transition() : rects) .attr('width', (id) => { return widths[id]; }) .attr('height', (id) => { return heights[id]; }) .attr('x', xForLegendRect) .attr('y', yForLegendRect); tiles = $$.legend.selectAll('line.' + CLASS.legendItemTile) .data(targetIds); (withTransition ? tiles.transition() : tiles) .style('stroke', $$.color) .attr('x1', x1ForLegendTile) .attr('y1', yForLegendTile) .attr('x2', x2ForLegendTile) .attr('y2', yForLegendTile); if (background) { (withTransition ? background.transition() : background) .attr('height', $$.getLegendHeight() - 12) .attr('width', maxWidth * (step + 1) + 10); } // toggle legend state $$.legend.selectAll('.' + CLASS.legendItem) .classed(CLASS.legendItemHidden, (id) => { return !$$.isTargetToShow(id); }); // Update all to reflect change of legend $$.updateLegendItemWidth(maxWidth); $$.updateLegendItemHeight(maxHeight); $$.updateLegendStep(step); // Update size and scale $$.updateSizes(); $$.updateScales(); $$.updateSvgSize(); // Update g positions $$.transformAll(withTransitionForTransform, transitions); $$.legendHasRendered = true; }; export { initLegend, updateLegendWithDefaults, updateSizeForLegend, transformLegend, updateLegendStep, updateLegendItemWidth, updateLegendItemHeight, getLegendWidth, getLegendHeight, opacityForLegend, opacityForUnfocusedLegend, toggleFocusLegend, revertLegend, showLegend, hideLegend, clearLegendItemTextBoxCache, updateLegend, };