UNPKG

@legumeinfo/web-components

Version:

Web Components for the Legume Information System and other AgBio databases

285 lines 11.3 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var LisLegendElement_1; import { html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { createRef, ref } from 'lit/directives/ref.js'; import { LisResizeObserverController } from '../controllers'; import { globalSubstitution } from '../utils/decorators'; /** * @htmlElement `<lis-legend-element>` * * A Web Component that draws a legend for data provided as a {@link LegendData | `LegendData`} * object. Note that the component fills all of the available horizontal space and will * automatically redraw if the width of its parent element changes. * * @example * The `<lis-legend-element>` tag requires <b>version 7</b> of {@link https://d3js.org/ | D3}. * To allow multiple versions of D3 to be used on the same page, the * {@link LisLegendElement | `LisLegendElement`} class uses the global `d3v7` variable if it has * been set. Otherwise it uses the global `d3` variable by default. The following is an example of * how to include D3 in the page and set the `d3v7` variable: * ```html * <!-- head --> * * <!-- D3 version 7 --> * <script src='http://d3js.org/d3.v7.min.js'></script> * * <!-- another version of D3 --> * <script type='text/javascript'> * var d3v7 = d3; * window.d7 = undefined; * </script> * <script src='http://d3js.org/d3.v3.min.js'></script> * * <!-- body --> * * <!-- add the Web Component to your HTML --> * <lis-legend-element></lis-legend-element> * ``` * * @example * {@link !HTMLElement | `HTMLElement`} properties can only be set via JavaScript. This means the * {@link data | `data`}, {@link colorFunction | `colorFunction`}, and * {@link clickFunction | `nodeClickFunction`} properties must be set on a `<lis-legend-element>` * tag's instance of the {@link LisLegendElement | `LisLegendElement`} class. For example: * ```html * <!-- add the Web Component to your HTML --> * <lis-legend-element id="legend"></lis-legend-element> * * <!-- configure the Web Component via JavaScript --> * <script type="text/javascript"> * // legend data * const data = [ * {label: 'Acacia crassicarpa'}, * {label: 'Aeschynomene evenia'}, * {label: 'Apios americana'}, * ]; * // returns a color given an entry * function color(entry) { * // returns a color for the given label * } * // click callback that gets passed the clicked entry * function click(entry) { * // do something * } * // get the legend element * const legendElement = document.getElementById('legend'); * // set the element's properties * phylotreeElement.data = data; * phylotreeElement.colorFunction = color; * phylotreeElement.clickFunction = click; * </script> * ``` * * @example * The {@link layout | `layout`}, {@link glyph | `glyph`}, and {@link position | `position`} * properties can be set as attributes of the `<lis-legend-element>` tag or as properties of the * tag's instance of the {@link LisLegendElement | `LisLegendElement`} class. * {@link layout | `layout`} sets the layout of the legend to `vertical` or `horizontal` (`vertical` * by default). {@link glyph | `glyph`} determines whether the glyph of each entry will be drawn as * a circle or a square (`circle` by default). And {@link position | `position`} determines the * position of the glyph relative to the label (`left` by default). For example: * ```html * <!-- add the Web Component to your HTML --> * <lis-legend-element * layout="horizontal" * glyph="square" * position="contain" * ></lis-legend-element> * <lis-legend-element id="legend"></lis-legend-element> * * <!-- configure the Web Component via JavaScript --> * <script type="text/javascript"> * // get the legend element * const legendElement = document.getElementById('legend'); * // set the element's properties * legendElement.layout = 'horizontal'; * legendElement.glyph = 'square'; * legendElement.position = 'contain'; * </script> * ``` */ let LisLegendElement = LisLegendElement_1 = class LisLegendElement extends LitElement { constructor() { super(...arguments); // bind to the tree container div element in the template this._legendContainerRef = createRef(); // a controller that allows element resize events to be observed this.resizeObserverController = new LisResizeObserverController(this, this._resize); /** * The layout the legend should be drawn in. * * @attribute */ this.layout = 'vertical'; /** * The glyph to use for each element in the legend. * * @attribute */ this.glyph = 'circle'; /** * Determines the position of the glyph relative to the label. * * @attribute */ this.position = 'left'; /** * The legend data. * * @attribute */ this.data = []; } // disable shadow DOM to inherit global styles createRenderRoot() { return this; } _resize(entries) { entries.forEach((entry) => { // TODO: compare legend width to container width //const drawnWidth = this._tree?.layout().width(); if (entry.target == this._legendContainerRef.value) { this.requestUpdate(); } }); } _legendContainerReady() { if (this._legendContainerRef.value) { this.resizeObserverController.observe(this._legendContainerRef.value); } } render() { this._drawLegend(); return html ` <div style="overflow: hidden;" ${ref(this._legendContainerRef)} ${ref(this._legendContainerReady)} ></div>`; } _legendWidth() { if (this._legendContainerRef.value === undefined) { return 0; } // compenstate for sub-container margins return this._legendContainerRef.value.offsetWidth; } _drawLegend() { if (this._legendContainerRef.value === undefined) { return; } // reset the container this._legendContainerRef.value.innerHTML = ''; // create the SVG element const width = this._legendWidth(); const svg = d3.create('svg').attr('width', width); this._legendContainerRef.value.append(svg.node()); // variables const radius = this.glyph == 'circle' ? LisLegendElement_1.GLYPH_SIZE / 2 : 0; const padding = Math.max(radius, LisLegendElement_1.GLYPH_MARGIN); const color = this.position == 'contain' ? '#FFFFFF' : 'inherit'; // add a group for each entry let x = 0; let row = 0; this.data.forEach((e, i) => { // create the entry const entry = svg.append('g'); if (this.clickFunction !== undefined) { entry .style('cursor', 'pointer') .on('click', () => { var _a; return (_a = this.clickFunction) === null || _a === void 0 ? void 0 : _a.call(this, e); }); } // add label let offset = LisLegendElement_1.GLYPH_MARGIN; // position == 'right' if (this.position == 'contain') { offset = padding; } else if (this.position == 'left') { offset += LisLegendElement_1.GLYPH_SIZE; } const text = entry .append('text') .attr('line-height', LisLegendElement_1.GLYPH_SIZE) .attr('font-size', LisLegendElement_1.GLYPH_SIZE / 1.2) // 1.2 is roughly what web browsers use .style('dominant-baseline', 'middle') .attr('x', offset) .attr('y', LisLegendElement_1.GLYPH_SIZE / 2) .text(e.label) .style('fill', color); // add glyph const textWidth = text.node().getComputedTextLength(); let w = LisLegendElement_1.GLYPH_SIZE; if (this.position == 'contain') { w = textWidth + padding * 2; } if (this.position == 'right') { offset += textWidth + LisLegendElement_1.GLYPH_MARGIN; } else { offset = 0; } entry .append('rect') .attr('width', w) .attr('height', LisLegendElement_1.GLYPH_SIZE) .attr('x', offset) .attr('rx', radius) .style('fill', () => { var _a; return e.color || ((_a = this.colorFunction) === null || _a === void 0 ? void 0 : _a.call(this, e)); }); text.raise(); // set the group position now that we know its size if (this.layout == 'horizontal') { const paddedWidth = entry.node().getBBox().width + LisLegendElement_1.GLYPH_MARGIN; if (x + paddedWidth > width) { x = 0; row += 1; } const y = row * LisLegendElement_1.GLYPH_SIZE + (row + 1) * LisLegendElement_1.GLYPH_MARGIN; entry.attr('transform', `translate(${x}, ${y})`); x += paddedWidth; } else { const y = i * LisLegendElement_1.GLYPH_SIZE + (i + 1) * LisLegendElement_1.GLYPH_MARGIN; entry.attr('transform', `translate(0, ${y})`); } }); // set the SVG height now that all element have been added const n = this.layout == 'horizontal' ? row + 1 : this.data.length; const height = n * LisLegendElement_1.GLYPH_SIZE + (n + 1) * LisLegendElement_1.GLYPH_MARGIN; svg.attr('height', height); } }; LisLegendElement.GLYPH_MARGIN = 5; LisLegendElement.GLYPH_SIZE = 15; __decorate([ property() ], LisLegendElement.prototype, "layout", void 0); __decorate([ property() ], LisLegendElement.prototype, "glyph", void 0); __decorate([ property() ], LisLegendElement.prototype, "position", void 0); __decorate([ property({ attribute: false }) ], LisLegendElement.prototype, "data", void 0); __decorate([ property({ type: Function, attribute: false }) ], LisLegendElement.prototype, "colorFunction", void 0); __decorate([ property({ type: Function, attribute: false }) ], LisLegendElement.prototype, "clickFunction", void 0); __decorate([ globalSubstitution('d3', 'd3v7') ], LisLegendElement.prototype, "_drawLegend", null); LisLegendElement = LisLegendElement_1 = __decorate([ customElement('lis-legend-element') ], LisLegendElement); export { LisLegendElement }; //# sourceMappingURL=lis-legend-element.js.map