Util.each(items, function(item, index) { const checked = item.checked; const value = self._formatItemValue(item.value); const markerColor = item.marker.fill || item.marker.stroke; const color = checked ? markerColor : unCheckedColor; let domStr; if (Util.isFunction(itemTpl)) { domStr = itemTpl(value, color, checked, index); } else { domStr = itemTpl; } const itemDiv = Util.substitute(domStr, { index, checked: checked ? 'checked' : 'unChecked', value, color, originColor: markerColor, originValue: item.value }); const itemDom = DomUtil.createDom(itemDiv); const markerDom = findNodeByClass(itemDom, MARKER_CLASS); DomUtil.modifyCSS(itemDom, itemStyle); markerDom && DomUtil.modifyCSS(markerDom, markerStyle); if (!checked) { itemDom.style.color = unCheckedColor; if (markerDom) { markerDom.style.backgroundColor = unCheckedColor; } } itemListDom.appendChild(itemDom); });
_setDomPosition(parentDom, childDom, point) { const self = this; const alignX = self.alignX; const alignY = self.alignY; const domWidth = DomUtil.getOuterWidth(childDom); const domHeight = DomUtil.getOuterHeight(childDom); const position = { x: point.x, y: point.y }; if (alignX === 'middle' && alignY === 'top') { position.x -= Util.round(domWidth / 2); } else if (alignX === 'middle' && alignY === 'bottom') { position.x -= Util.round(domWidth / 2); position.y -= Util.round(domHeight); } else if (alignX === 'left' && alignY === 'bottom') { position.y -= Util.round(domHeight); } else if (alignX === 'left' && alignY === 'middle') { position.y -= Util.round(domHeight / 2); } else if (alignX === 'left' && alignY === 'top') { position.x = point.x; position.y = point.y; } else if (alignX === 'right' && alignY === 'bottom') { position.x -= Util.round(domWidth); position.y -= Util.round(domHeight); } else if (alignX === 'right' && alignY === 'middle') { position.x -= Util.round(domWidth); position.y -= Util.round(domHeight / 2); } else if (alignX === 'right' && alignY === 'top') { position.x -= Util.round(domWidth); } else { // 默认位于中心点 position.x -= Util.round(domWidth / 2); position.y -= Util.round(domHeight / 2); } if (self.offsetX) { position.x += self.offsetX; } if (self.offsetY) { position.y += self.offsetY; } DomUtil.modifyCSS(parentDom, { position: 'absolute', top: Math.round(position.y) + 'px', left: Math.round(position.x) + 'px', visibility: 'visible', zIndex: self.zIndex }); }
_createText(cfg) { const htmlTemplate = this.get('htmlTemplate'); let customDiv = this.get('customDiv'); let labelShape; if (htmlTemplate) { if (!customDiv) { const containerTpl = this.get('_containerTpl'); const wrapper = this.get('canvas').get('el').parentNode; customDiv = DomUtil.createDom(containerTpl); wrapper.style.position = 'relative'; wrapper.appendChild(customDiv); this.set('customDiv', customDiv); } const node = this._createDom(cfg); customDiv.appendChild(node); this._setCustomPosition(cfg, node); } else { const origin = cfg.point; delete cfg.point; // 临时解决,否则影响动画 labelShape = this.addShape('text', { attrs: cfg }); labelShape.setSilent('origin', origin); labelShape.name = 'label'; // 用于事件标注 this.get('appendInfo') && labelShape.setSilent('appendInfo', this.get('appendInfo')); return labelShape; } }
_addItem(item, index) { const itemTpl = this.get('itemTpl'); // TODO: 有可能是个回调函数 const itemDiv = Util.substitute(itemTpl, Util.mix({ index }, item)); const itemDOM = DomUtil.createDom(itemDiv); DomUtil.modifyCSS(itemDOM, this.get(LIST_ITEM_CLASS)); const markerDom = find(itemDOM, MARKER_CLASS); if (markerDom) { DomUtil.modifyCSS(markerDom, this.get(MARKER_CLASS)); } return itemDOM; }
_initTooltipWrapper() { const self = this; const containerTpl = self.get('containerTpl'); const outterNode = self.get('canvas').get('el').parentNode; let container; if (/^\#/.test(containerTpl)) { // 如果传入 dom 节点的 id const id = containerTpl.replace('#', ''); container = document.getElementById(id); } else { container = DomUtil.createDom(containerTpl); DomUtil.modifyCSS(container, self.get(CONTAINER_CLASS)); outterNode.appendChild(container); outterNode.style.position = 'relative'; } self.set('container', container); }
getHeight() { if (this.get('useHtml')) { return DomUtil.getOuterHeight(this.get('legendWrapper')); } return super.getHeight(); }
render(coord, group) { const self = this; const position = self.parsePoint(coord, self.position); const parentNode = group.get('canvas').get('el').parentNode; const wrapperNode = DomUtil.createDom('<div class="g-guide"></div>'); parentNode.appendChild(wrapperNode); let html = self.html; if (Util.isFunction(html)) { html = html(self.xScales, self.yScales); } const htmlNode = DomUtil.createDom(html); wrapperNode.appendChild(htmlNode); self._setDomPosition(wrapperNode, htmlNode, position); self.el = wrapperNode; }
_setCustomPosition(cfg, htmlDom) { const textAlign = cfg.textAlign || 'left'; let top = cfg.y; let left = cfg.x; const width = DomUtil.getOuterWidth(htmlDom); const height = DomUtil.getOuterHeight(htmlDom); top = top - height / 2; if (textAlign === 'center') { left = left - width / 2; } else if (textAlign === 'right') { left = left - width; } htmlDom.style.top = parseInt(top, 10) + 'px'; htmlDom.style.left = parseInt(left, 10) + 'px'; }
move(x, y) { if (this.get('useHtml') && !(/^\#/.test(this.get('container')))) { DomUtil.modifyCSS(this.get('legendWrapper'), { left: x + 'px', top: y + 'px' }); } else { super.move(x, y); } }
_createDom(cfg) { const itemTpl = this.get('_itemTpl'); const htmlTemplate = this.get('htmlTemplate'); if (Util.isString(htmlTemplate)) { cfg.text = Util.substitute(htmlTemplate, { text: cfg.text }); } const str = Util.substitute(itemTpl, { text: cfg.text }); return DomUtil.createDom(str); }
_renderTooltip() { const self = this; const showTitle = self.get('showTitle'); const titleContent = self.get('titleContent'); const container = self.get('container'); const titleDom = find(container, TITLE_CLASS); const listDom = find(container, LIST_CLASS); const items = self.get('items'); self._clearDom(); if (titleDom && showTitle) { DomUtil.modifyCSS(titleDom, self.get(TITLE_CLASS)); titleDom.innerHTML = titleContent; } if (listDom) { DomUtil.modifyCSS(listDom, self.get(LIST_CLASS)); Util.each(items, (item, index) => { listDom.appendChild(self._addItem(item, index)); }); } }
setPosition(x, y, target) { const container = this.get('container'); const crossLineShapeX = this.get('crossLineShapeX'); const crossLineShapeY = this.get('crossLineShapeY'); const crosshairsRectShape = this.get('crosshairsRectShape'); let endx = x; let endy = y; // const outterNode = this.get('canvas').get('el').parentNode; const outterNode = this.get('canvas').get('el'); const viewWidth = DomUtil.getWidth(outterNode); const viewHeight = DomUtil.getHeight(outterNode); let offset = this.get('offset'); let position; if (this.get('position')) { position = calcTooltipPosition(x, y, this.get('position'), container, target); x = position[0]; y = position[1]; } else if (!this.get('position')) { position = refixTooltipPosition(x, y, container, viewWidth, viewHeight); x = position[0]; y = position[1]; } if (this.get('inPlot')) { // tooltip 必须限制在绘图区域内 const plotRange = this.get('plotRange'); position = confineTooltipPosition(x, y, container, plotRange); x = position[0]; y = position[1]; } if (this.get('x') !== x || this.get('y') !== y) { const markerItems = this.get('markerItems'); if (!Util.isEmpty(markerItems)) { endx = markerItems[0].x; endy = markerItems[0].y; } if (crossLineShapeY) { // 第一次进入时,画布需要单独绘制,所以需要先设定corss的位置 crossLineShapeY.move(endx, 0); } if (crossLineShapeX) { crossLineShapeX.move(0, endy); } if (crosshairsRectShape) { const isTransposed = this.get('isTransposed'); const items = this.get('items'); const firstItem = items[0]; const lastItem = items[items.length - 1]; const dim = isTransposed ? 'y' : 'x'; const attr = isTransposed ? 'height' : 'width'; let startDim = firstItem[dim]; if (items.length > 1 && firstItem[dim] > lastItem[dim]) { startDim = lastItem[dim]; } if (this.get('crosshairs').width) { // 用户定义了 width crosshairsRectShape.attr(dim, startDim - this.get('crosshairs').width / 2); crosshairsRectShape.attr(attr, this.get('crosshairs').width); } else { offset = (firstItem.size / 2 + firstItem.size / 4) || 10; crosshairsRectShape.attr(dim, startDim - offset); if (items.length === 1) { crosshairsRectShape.attr(attr, firstItem.size + firstItem.size / 2); } else { const lastItem = items[items.length - 1]; crosshairsRectShape.attr(attr, Math.abs(lastItem[dim] - firstItem[dim]) + 2 * offset); } } } const follow = this.get('follow'); container.style.left = follow ? (x + 'px') : 0; container.style.top = follow ? (y + 'px') : 0; } }
getWidth() { if (this.get('useHtml')) { return DomUtil.getOuterWidth(this.get('legendWrapper')); } return super.getWidth(); }
_renderHTML() { const self = this; const canvas = self.get('canvas'); const outterNode = canvas.get('el').parentNode; const title = this.get('title'); const containerTpl = self.get('containerTpl'); const legendWrapper = DomUtil.createDom(containerTpl); const titleDom = findNodeByClass(legendWrapper, TITLE_CLASS); const itemListDom = findNodeByClass(legendWrapper, LIST_CLASS); const unCheckedColor = self.get('unCheckColor'); const mode = self.get('selectedMode'); const LEGEND_STYLE = Global.legend.html; DomUtil.modifyCSS(itemListDom, Util.mix({}, LEGEND_STYLE[LIST_CLASS], self.get(LIST_CLASS))); if (titleDom) { if (title && title.text) { titleDom.innerHTML = title.text; DomUtil.modifyCSS(titleDom, Util.mix({}, LEGEND_STYLE[TITLE_CLASS], self.get(TITLE_CLASS))); } else { legendWrapper.removeChild(titleDom); } } // 开始渲染图例项 const items = self.get('items'); let itemTpl = self.get('_defaultItemTpl'); const userItemTpl = self.get('itemTpl'); if (userItemTpl && userItemTpl !== itemTpl) { itemTpl = userItemTpl; } if (self.get('reversed')) { items.reverse(); } const position = self.get('position'); const itemStyle = Util.mix({}, LEGEND_STYLE[ITEM_CLASS], { display: (position === 'right' || position === 'left') ? 'block' : 'inline-block' }, self.get(ITEM_CLASS)); const markerStyle = Util.mix({}, LEGEND_STYLE[MARKER_CLASS], self.get(MARKER_CLASS)); Util.each(items, function(item, index) { const checked = item.checked; const value = self._formatItemValue(item.value); const markerColor = item.marker.fill || item.marker.stroke; const color = checked ? markerColor : unCheckedColor; let domStr; if (Util.isFunction(itemTpl)) { domStr = itemTpl(value, color, checked, index); } else { domStr = itemTpl; } const itemDiv = Util.substitute(domStr, { index, checked: checked ? 'checked' : 'unChecked', value, color, originColor: markerColor, originValue: item.value }); const itemDom = DomUtil.createDom(itemDiv); const markerDom = findNodeByClass(itemDom, MARKER_CLASS); DomUtil.modifyCSS(itemDom, itemStyle); markerDom && DomUtil.modifyCSS(markerDom, markerStyle); if (!checked) { itemDom.style.color = unCheckedColor; if (markerDom) { markerDom.style.backgroundColor = unCheckedColor; } } itemListDom.appendChild(itemDom); }); if (self.get('clickable')) { const childNodes = itemListDom.childNodes; // 注册事件 legendWrapper.onclick = ev => { const target = ev.target; let targetClass = target.className; targetClass = targetClass.split(' '); if (targetClass.indexOf(CONTAINER_CLASS) > -1 || targetClass.indexOf(LIST_CLASS) > -1) { return; } const parentDom = getParentNode(target, ITEM_CLASS); const textDom = findNodeByClass(parentDom, TEXT_CLASS); const markerDom = findNodeByClass(parentDom, MARKER_CLASS); const clickedItem = findItem(items, parentDom.getAttribute('data-value')); if (!clickedItem) { return; } // update checked status clickedItem.checked = (mode === 'single') ? true : !(clickedItem.checked); const domClass = parentDom.className; const originColor = parentDom.getAttribute('data-color'); if (mode === 'single') { // 单选模式 // 其他图例项全部置灰 Util.each(childNodes, child => { if (child !== parentDom) { const childMarkerDom = findNodeByClass(child, MARKER_CLASS); childMarkerDom.style.backgroundColor = unCheckedColor; child.className = Util.replace(child.className, 'checked', 'unChecked'); child.style.color = unCheckedColor; const childItem = findItem(items, child.getAttribute('data-value')); childItem.checked = false; } else { if (textDom) { textDom.style.color = self.get('textStyle').fill; } if (markerDom) { markerDom.style.backgroundColor = originColor; } parentDom.className = Util.replace(domClass, 'unChecked', 'checked'); } }); } else { // 混合模式 const clickedItemChecked = domClass.includes('checked'); let count = 0; Util.each(childNodes, child => { if (child.className.includes('checked')) { count++; } }); if (!this.get('allowAllCanceled') && clickedItemChecked && count === 1) { return; } if (clickedItemChecked) { if (markerDom) { markerDom.style.backgroundColor = unCheckedColor; } parentDom.className = Util.replace(domClass, 'checked', 'unChecked'); parentDom.style.color = unCheckedColor; } else { if (markerDom) { markerDom.style.backgroundColor = originColor; } parentDom.className = Util.replace(domClass, 'unChecked', 'checked'); parentDom.style.color = self.get('textStyle').fill; } } self.emit('itemclick', { item: clickedItem, currentTarget: parentDom, checked: (mode === 'single') ? true : clickedItem.checked }); }; } if (self.get('hoverable')) { legendWrapper.onmousemove = ev => { const target = ev.target; let targetClass = target.className; targetClass = targetClass.split(' '); if (targetClass.indexOf(CONTAINER_CLASS) > -1 || targetClass.indexOf(LIST_CLASS) > -1) { return; } const parentDom = getParentNode(target, ITEM_CLASS); const domClass = parentDom.className; const hoveredItem = findItem(items, parentDom.getAttribute('data-value')); if (hoveredItem && domClass.includes('checked')) { self.emit('itemhover', { item: hoveredItem, currentTarget: parentDom, checked: hoveredItem.checked }); } else if (!hoveredItem) { self.emit('itemunhover', ev); } }; legendWrapper.onmouseout = ev => { self.emit('itemunhover', ev); }; } let container = self.get('container'); if (/^\#/.test(container)) { // 如果传入 dom 节点的 id const id = container.replace('#', ''); container = document.getElementById(id); // container.style.position = 'relative'; container.appendChild(legendWrapper); } else { const position = self.get('position'); const canvas = self.get('canvas'); let rangeStyle = {}; if (position === 'left' || position === 'right') { rangeStyle = { maxHeight: (self.get('maxLength') || canvas.get('height')) + 'px' }; } else { rangeStyle = { maxWidth: (self.get('maxLength') || canvas.get('width')) + 'px' }; } DomUtil.modifyCSS(legendWrapper, Util.mix({}, LEGEND_STYLE[CONTAINER_CLASS], rangeStyle, self.get(CONTAINER_CLASS))); outterNode.appendChild(legendWrapper); } self.set('legendWrapper', legendWrapper); }