|
|
|
c3_chart_internal_fn.initLegend = function () {
|
|
|
|
var $$ = this;
|
|
|
|
$$.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();
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateLegendWithDefaults = function () {
|
|
|
|
var $$ = this;
|
|
|
|
$$.updateLegend($$.mapToIds($$.data.targets), {withTransform: false, withTransitionForTransform: false, withTransition: false});
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateSizeForLegend = function (legendHeight, legendWidth) {
|
|
|
|
var $$ = 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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.transformLegend = function (withTransition) {
|
|
|
|
var $$ = this;
|
|
|
|
(withTransition ? $$.legend.transition() : $$.legend).attr("transform", $$.getTranslate('legend'));
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateLegendStep = function (step) {
|
|
|
|
this.legendStep = step;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateLegendItemWidth = function (w) {
|
|
|
|
this.legendItemWidth = w;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateLegendItemHeight = function (h) {
|
|
|
|
this.legendItemHeight = h;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.getLegendWidth = function () {
|
|
|
|
var $$ = this;
|
|
|
|
return $$.config.legend_show ? $$.isLegendRight || $$.isLegendInset ? $$.legendItemWidth * ($$.legendStep + 1) : $$.currentWidth : 0;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.getLegendHeight = function () {
|
|
|
|
var $$ = this, h = 0;
|
|
|
|
if ($$.config.legend_show) {
|
|
|
|
if ($$.isLegendRight) {
|
|
|
|
h = $$.currentHeight;
|
|
|
|
} else {
|
|
|
|
h = Math.max(20, $$.legendItemHeight) * ($$.legendStep + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return h;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.opacityForLegend = function (legendItem) {
|
|
|
|
return legendItem.classed(CLASS.legendItemHidden) ? null : 1;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.opacityForUnfocusedLegend = function (legendItem) {
|
|
|
|
return legendItem.classed(CLASS.legendItemHidden) ? null : 0.3;
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.toggleFocusLegend = function (targetIds, focus) {
|
|
|
|
var $$ = this;
|
|
|
|
targetIds = $$.mapToTargetIds(targetIds);
|
|
|
|
$$.legend.selectAll('.' + CLASS.legendItem)
|
|
|
|
.filter(function (id) { return targetIds.indexOf(id) >= 0; })
|
|
|
|
.classed(CLASS.legendItemFocused, focus)
|
|
|
|
.transition().duration(100)
|
|
|
|
.style('opacity', function () {
|
|
|
|
var opacity = focus ? $$.opacityForLegend : $$.opacityForUnfocusedLegend;
|
|
|
|
return opacity.call($$, $$.d3.select(this));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.revertLegend = function () {
|
|
|
|
var $$ = this, d3 = $$.d3;
|
|
|
|
$$.legend.selectAll('.' + CLASS.legendItem)
|
|
|
|
.classed(CLASS.legendItemFocused, false)
|
|
|
|
.transition().duration(100)
|
|
|
|
.style('opacity', function () { return $$.opacityForLegend(d3.select(this)); });
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.showLegend = function (targetIds) {
|
|
|
|
var $$ = 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)); });
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.hideLegend = function (targetIds) {
|
|
|
|
var $$ = 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');
|
|
|
|
};
|
|
|
|
var legendItemTextBox = {};
|
|
|
|
c3_chart_internal_fn.clearLegendItemTextBoxCache = function () {
|
|
|
|
legendItemTextBox = {};
|
|
|
|
};
|
|
|
|
c3_chart_internal_fn.updateLegend = function (targetIds, options, transitions) {
|
|
|
|
var $$ = this, config = $$.config;
|
|
|
|
var xForLegend, xForLegendText, xForLegendRect, yForLegend, yForLegendText, yForLegendRect;
|
|
|
|
var paddingTop = 4, paddingRight = 10, maxWidth = 0, maxHeight = 0, posMin = 10, tileWidth = 15;
|
|
|
|
var l, totalLength = 0, offsets = {}, widths = {}, heights = {}, margins = [0], steps = {}, step = 0;
|
|
|
|
var withTransition, withTransitionForTransform;
|
|
|
|
var hasFocused = $$.legend.selectAll('.' + CLASS.legendItemFocused).size();
|
|
|
|
var texts, rects, tiles, background;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
return legendItemTextBox[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
function updatePositions(textElement, id, index) {
|
|
|
|
var reset = index === 0, isLast = index === targetIds.length - 1,
|
|
|
|
box = getTextBox(textElement, id),
|
|
|
|
itemWidth = box.width + tileWidth + (isLast && !($$.isLegendRight || $$.isLegendInset) ? 0 : paddingRight),
|
|
|
|
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(function (id) { widths[id] = maxWidth; });
|
|
|
|
Object.keys(heights).forEach(function (id) { heights[id] = maxHeight; });
|
|
|
|
margin = (areaLength - maxLength * targetIds.length) / 2;
|
|
|
|
if (margin < posMin) {
|
|
|
|
totalLength = 0;
|
|
|
|
step = 0;
|
|
|
|
targetIds.forEach(function (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) + 14; };
|
|
|
|
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; };
|
|
|
|
|
|
|
|
// Define g for legend area
|
|
|
|
l = $$.legend.selectAll('.' + CLASS.legendItem)
|
|
|
|
.data(targetIds)
|
|
|
|
.enter().append('g')
|
|
|
|
.attr('class', function (id) { return $$.generateClass(CLASS.legendItem, id); })
|
|
|
|
.style('visibility', function (id) { return $$.isLegendToShow(id) ? 'visible' : 'hidden'; })
|
|
|
|
.style('cursor', 'pointer')
|
|
|
|
.on('click', function (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) {
|
|
|
|
$$.d3.select(this).classed(CLASS.legendItemFocused, true);
|
|
|
|
if (!$$.transiting && $$.isTargetToShow(id)) {
|
|
|
|
$$.api.focus(id);
|
|
|
|
}
|
|
|
|
if (config.legend_item_onmouseover) {
|
|
|
|
config.legend_item_onmouseover.call($$, id);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.on('mouseout', function (id) {
|
|
|
|
$$.d3.select(this).classed(CLASS.legendItemFocused, false);
|
|
|
|
$$.api.revert();
|
|
|
|
if (config.legend_item_onmouseout) {
|
|
|
|
config.legend_item_onmouseout.call($$, id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
l.append('text')
|
|
|
|
.text(function (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('rect')
|
|
|
|
.attr("class", CLASS.legendItemTile)
|
|
|
|
.style("pointer-events", "none")
|
|
|
|
.style('fill', $$.color)
|
|
|
|
.attr('x', $$.isLegendRight || $$.isLegendInset ? xForLegendText : -200)
|
|
|
|
.attr('y', $$.isLegendRight || $$.isLegendInset ? -200 : yForLegend)
|
|
|
|
.attr('width', 10)
|
|
|
|
.attr('height', 10);
|
|
|
|
|
|
|
|
// 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(function (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', function (id) { return widths[id]; })
|
|
|
|
.attr('height', function (id) { return heights[id]; })
|
|
|
|
.attr('x', xForLegendRect)
|
|
|
|
.attr('y', yForLegendRect);
|
|
|
|
|
|
|
|
tiles = $$.legend.selectAll('rect.' + CLASS.legendItemTile)
|
|
|
|
.data(targetIds);
|
|
|
|
(withTransition ? tiles.transition() : tiles)
|
|
|
|
.style('fill', $$.color)
|
|
|
|
.attr('x', xForLegend)
|
|
|
|
.attr('y', yForLegend);
|
|
|
|
|
|
|
|
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, function (id) { return !$$.isTargetToShow(id); })
|
|
|
|
.transition()
|
|
|
|
.style('opacity', function (id) {
|
|
|
|
var This = $$.d3.select(this);
|
|
|
|
if ($$.isTargetToShow(id)) {
|
|
|
|
return !hasFocused || This.classed(CLASS.legendItemFocused) ? $$.opacityForLegend(This) : $$.opacityForUnfocusedLegend(This);
|
|
|
|
} else {
|
|
|
|
return null; // c3-legend-item-hidden will be applied
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
};
|