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.
358 lines
14 KiB
358 lines
14 KiB
8 years ago
|
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,
|
||
|
};
|