UNPKG

d2b

Version:

A d3-based reusable chart library.

169 lines (132 loc) 5.56 kB
import { symbol as d3Symbol, symbolCircle } from 'd3-shape'; import { select } from 'd3-selection'; import 'd3-transition'; import base from '../model/base'; import color from '../util/color'; export default function () { const $$ = {}; const symbol = d3Symbol().size(80); const legend = function (context) { const selection = context.selection? context.selection() : context; let leg = selection.selectAll('.d2b-legend').data(d => [d]); const legEnter = leg.enter() .append('div') .attr('class', 'd2b-legend'); leg = leg.merge(legEnter).classed('d2b-vertical', $$.vertical); let legItem = leg.selectAll('.d2b-legend-item').data($$.values, $$.key), legItemExit = legItem.exit(); const legItemEnter = legItem.enter() .append('div') .attr('class', 'd2b-legend-item') .style('opacity', 0); legItemEnter.append('div').attr('class', 'd2b-legend-icon'); legItemEnter.append('div').attr('class', 'd2b-legend-content'); legItem = legItem.merge(legItemEnter).order(); legItem.select('.d2b-legend-content').html($$.html); if (context !== selection) { legItem = legItem.transition(context); legItemExit = legItemExit.transition(context).style('opacity', 0); } legItem .style('opacity', 1) .each(function (d, i) { // legend item customization let item = select(this); const color = $$.color(d, i), empty = $$.empty(d, i); // legend icon customization const icon = $$.icon(d, i), iconDiv = item.select('.d2b-legend-icon'), fa = [], shape = [], rect = iconDiv.node().getBoundingClientRect(), size = { width: rect.width, height: rect.height }, center = { x: size.width / 2, y: size.height / 2}; if (typeof icon === 'string') fa.push(icon); else shape.push(icon); let svgIcon = iconDiv.selectAll('.d2b-legend-svg-icon').data(d => [d]); let svgIconEnter = svgIcon.enter().append('svg'); svgIconEnter .attr('class', 'd2b-legend-svg-icon') .attr('width', size.width) .attr('height', size.height); svgIcon = svgIcon.merge(svgIconEnter); let svgFa = svgIcon.selectAll('text').data(fa), svgFaEnter = svgFa.enter().append('text'); svgFa.exit().remove(); svgFaEnter.append('tspan'); svgFa = svgFa.merge(svgFaEnter); svgFa.select('tspan').text(d => d); let svgShape = svgIcon.selectAll('path').data(shape); svgShape.exit().remove(); svgShape = svgShape.merge(svgShape.enter().append('path')); svgShape.attr('d', d => symbol.type(d)()); if (context !== selection) { item = item.transition(context); svgFa = svgFa.transition(context); svgShape = svgShape.transition(context); } item.style('border-color', color); svgFa .style('stroke', color) .style('fill', empty? 'white' : color) .attr('transform', `translate(${center.x},${center.y * 1.65})`); svgShape .style('stroke', color) .style('fill', empty? 'white' : color) .attr('transform', `translate(${center.x},${center.y})`); }); // bind events leg.each(function (d, i) { const allowEmptied = $$.allowEmptied(d, i), items = select(this).selectAll('.d2b-legend-item'), setAllEmpty = function (state) { items.each(function(dd, ii) { $$.setEmpty(dd, ii, state); }); }, allEmpty = function () { let allEmpty = true; items.each(function (dd, ii) { if (!$$.empty(dd, ii)) allEmpty = false; }); return allEmpty; }, click = function (d, i) { $$.setEmpty(d, i, !$$.empty(d, i)); if (!allowEmptied && allEmpty()) setAllEmpty(false); selection.call(legend); select(this.parentNode).dispatch('change', {bubbles: true}); }, dblclick = function (d, i) { setAllEmpty(true); $$.setEmpty(d, i, false); selection.call(legend); select(this.parentNode).dispatch('change', {bubbles: true}); }; items.each(function (d, i) { const clickable = $$.clickable(d, i), dblclickable = $$.dblclickable(d, i); select(this) .on('click', clickable? click: null) .on('dblclick', dblclickable? dblclick: null); }); }); selection.dispatch('legend-updated', {bubbles: true}); legItemExit.remove(); }; /* Inherit from base model */ base(legend, $$) .addPropFunctor('values', d => d) .addPropFunctor('key', (d, i) => i) .addPropFunctor('color', d => color(d.html)) .addPropFunctor('html', d => d.html) .addPropFunctor('icon', symbolCircle) .addPropFunctor('vertical', false) .addPropFunctor('allowEmptied', false) .addPropFunctor('clickable', false) .addPropFunctor('dblclickable', false) .addPropFunctor('empty', d => d.empty) .addPropFunctor('setEmpty', (d, i, state) => d.empty = state); return legend; }