我使用NVD3的lineChart来提供患者的进化概述'对治疗的反应。 以下是我试图实现的一个例子;在一系列的某些条目上添加图标(在这里使用字体真棒)(参见笑脸):
在考虑黑客攻击之前,我希望找到一个干净的解决方案:-)还没有找到一个。像使用jQuery来定位具有某些元的值并将图标附加到dom上的黑客听起来就像是......丑陋的黑客。
答案 0 :(得分:0)
除非有干净的&官方回答,我认为以下内容可视为解决方案。
此操作后获得的结果
让我开心。希望能帮助到你。为了避免我的实现特定需求的开销,让我们只在图形的第一个系列的每个节点上方显示一个“md-user”图标,以获得以下结果:
<强>说明强>:
假设有一个组件包含NVD3图元素,例如:
我-component.html 强>
我-component.scss 强>
my-component {
nvd3{
position: relative; // do NOT forget this one
.illustrations-wrapper{
position: absolute;
left: 0px; // otherwise breaks on mobile safari
.point-illustration{
position: absolute;
margin-bottom: 22px;
> .vertical-line{
position: absolute;
top: 28px;
border-right: 1px solid #666;
height: 14px;
}
> i {
position: absolute;
pointer-events: none; // without this your mouse hovering the graph wouldnt trigger highlights / popup
&.fa-2x{
left: -10px; // this gets the line centered in case of icons using the font-awesome fa-2x size modifier
}
}
}
}
}
}
my-component.ts:挂钩“在图表渲染后的事件”
private initGraphOptions(): void {
this.graphOptions = {
"chart": {
"type": "lineChart",
"callback": (chart) => {
if(!chart) return;
// Listen to click events
chart.lines.dispatch.on('elementClick', (e) => this.entryClicked(e[0].point.x) );
// Add some overlaying icons when rendering completes
chart.dispatch.on('renderEnd', () => this.onRenderComplete() );
// Customize tooltip
chart.interactiveLayer.tooltip.contentGenerator(this.tooltipContentGenerator);
return chart;
},
"height": 450,
"margin": { ... },
"useInteractiveGuideline": true,
"dispatch": {},
"xAxis": { ... }
"yDomain": [-100, 100],
"yAxis": { ... }
}
};
}
my-component.ts:渲染后逻辑
private onRenderComplete(): void {
this.ensurePresenceOfIllustrationsLayer() && this.renderGraphIcons();
}
private ensurePresenceOfIllustrationsLayer() : boolean {
// We wish to create a child element to our NVD3 element.
// In this case, I can identify my element thanks to a class named "score-graph".
// This is the current component's DOM element
const hostElement = this.elementRef.nativeElement;
const nvd3Element = hostElement.getElementsByClassName('score-graph')[0];
if(!nvd3Element) return false; // in case something went wrong
// Add a wrapper div to that nvd3 element. Ensure it has the "illustrations-wrapper" class to apply propre positionning (see scss)
const wrapper = this.renderer.createElement('div');
this.renderer.addClass(wrapper, 'illustrations-wrapper');
this.renderer.appendChild(nvd3Element, wrapper); // woa angular is just awesome
// Our nvd3 element now has two children: <svg> and <div.illustrations-wrapper>
return true;
}
private renderGraphIcons(): void {
if(!(this.data && this.data[0]) ) return;
// This is the current component's DOM element
const hostElement = this.elementRef.nativeElement;
// The illustration wrapper will hold en entry per point to illustrate
const illustrationWrapper = hostElement.getElementsByClassName('illustrations-wrapper') && hostElement.getElementsByClassName('illustrations-wrapper')[0];
if(!illustrationWrapper) return;
// We are looking for a class named after the series we wish to process illustrate. In this case, we need "nv-series-0"
// However, there's two elements bearing that class:
// - under "nv-groups", where lines are drawn
// - under "nv-scatterWrap", where nodes (dots) are rendered.
// We need the second one. Let's even take its first most relevant child with a unique class under the hostElement: "nv-scatter"
const scatter = hostElement.getElementsByClassName('nv-scatter') && hostElement.getElementsByClassName('nv-scatter')[0];
if(!scatter && !scatter.getElementsByClassName) return; // in case something went wrong
// Now there will only be one possible element when looking for class "nv-series-0"
// NB. you can also use the "classed" property of a series when defining your graph data to assign a custom class.
const targetSeries = scatter.getElementsByClassName('nv-series-0') && scatter.getElementsByClassName('nv-series-0')[0];
if(!targetSeries && !targetSeries.getElementsByClassName) return; // in case something went wrong
// Now it gets nice. We get the array of points (as DOM "path" elements)
const points: any[] = targetSeries.getElementsByClassName('nv-point');
// You think this is a dirty hack so far? Well, keep reading. It gets worse.
const seriesData = this.data[0];
if(points.length !== seriesData.values.length) return; // not likely, but you never know.
for(let i = 0; i < points.length; i++){
const point = points[i];
const pointData = seriesData.values[i];
let translationX = 0;
let translationY = 0;
try{
// How far is this point from the left border?
let translation = point.getAttribute('transform'); // this wll get you a string like "translate(0,180)" and we're looking for both values
translation = translation.replace('translate(', '').replace(')', '').split(','); // Ok I might rot for this ugly regex-lazy approach
// What's the actual possibly scaled-down height of the graph area? Answer: Get the Y translation applied to the x-axis
const nvx = hostElement.getElementsByClassName('nv-x')[0];
const xAxisTranslate = nvx.getAttribute('transform').replace('translate(', '').replace(')', '').split(','); // Same comment
translationX = Number.parseInt(translation[0]);
translationY = Number.parseInt(xAxisTranslate[1]) / 2;
}
catch(err){
}
const translationOffsetX = 0;
const translationOffsetY = 52; // "trial and error" will tell what's best here
// Let's add an illustration entry here
const illustration = this.renderer.createElement('div');
this.renderer.addClass(illustration, 'point-illustration')
this.renderer.setStyle(illustration, 'left', (translationX + translationOffsetX) + 'px');
this.renderer.setStyle(illustration, 'bottom', (translationY + translationOffsetY) + 'px');
this.renderer.appendChild(illustrationWrapper, illustration);
// Optional: add a a visual connexion between the actual chart line and the icon (define this first so that the icon overlaps)
const verticalLine = this.renderer.createElement('div');
this.renderer.addClass(verticalLine, 'vertical-line');
this.renderer.appendChild(illustration, verticalLine);
// Add the icon
const icon = this.renderer.createElement('i');
this.renderer.addClass(icon, 'fa');
this.renderer.addClass(icon, 'fa-user-md');
this.renderer.addClass(icon, 'fa-2x');
this.renderer.appendChild(illustration, icon);
}
}