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

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,
};