mirror of https://github.com/masayuki0812/c3.git
Quite good looking graph derived from d3.js
http://c3js.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
14 KiB
357 lines
14 KiB
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, |
|
};
|
|
|