juijs-chart
Version:
SVG-based JUI chart that can be used in the browser and Node.js. Support many types of charts. (Dashboard, Map, Topology, Full 3D)
257 lines (214 loc) • 10.9 kB
JavaScript
import jui from '../main.js';
export default {
name: "chart.widget.guideline",
extend: "chart.widget.core",
component: function() {
const _ = jui.include("util.base");
const GuideLineWidget = function(chart, axis, widget) {
const self = this;
const tw = 50, th = 18, ta = tw / 10; // x축 툴팁 넓이, 높이, 앵커 크기
const cp = 5, lrp = 5; // 본문 툴팁 패딩, 본물 툴팁 좌우 패딩
let brush;
let pl = 0, pt = 0; // 엑시스까지의 여백
let g, line, xTooltip, contentTooltip, points = {};
let tspan = [];
function printXAxisTooltip(index, text, message) {
if(!tspan[index]) {
const elem = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
text.element.appendChild(elem);
tspan[index] = elem;
}
tspan[index].textContent = message;
}
function getTextWidth(text, font) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width * 1.5;
}
this.drawBefore = function() {
brush = this.chart.get('brush', widget.brush);
// 위젯 옵션에 따라 엑시스 변경
axis = this.chart.axis(brush.axis);
// 엑시스 여백 값 가져오기
pl = chart.padding("left");
pt = chart.padding("top");
// 가이드라인 그리기
g = chart.svg.group({
visibility: "hidden"
}, function() {
if(_.typeCheck("function", widget.xFormat)) {
xTooltip = chart.svg.group({}, function () {
chart.svg.polygon({
fill: chart.theme("guidelineBalloonBackgroundColor"),
"fill-opacity": chart.theme("guidelineBalloonBackgroundOpacity"),
points: self.balloonPoints("bottom", tw, th, ta)
});
chart.text({
"font-size": chart.theme("guidelineBalloonFontSize"),
"fill": chart.theme("guidelineBalloonFontColor"),
"text-anchor": "middle",
x: tw / 2,
y: 17
});
}).translate(0, axis.area("height") + ta);
}
// 가이드라인 그리기
line = chart.svg.line({
x1: 0,
y1: 0,
x2: 0,
y2: axis.area("height"),
stroke: chart.theme("guidelineBorderColor"),
"stroke-width": chart.theme("guidelineBorderWidth"),
"stroke-dasharray": chart.theme("guidelineBorderDashArray"),
opacity: chart.theme("guidelineBorderOpacity")
});
// 포인트 그리기
brush.target.forEach((target, index) => {
points[target] = chart.svg.circle({
fill: chart.color(index),
stroke: chart.theme("guidelinePointBorderColor"),
"stroke-width": chart.theme("guidelinePointBorderWidth"),
r: chart.theme("guidelinePointRadius")
});
});
// 본문 툴팁 그리기
contentTooltip = chart.svg.group({}, function () {
chart.svg.rect({
fill: chart.theme("guidelineTooltipBackgroundColor"),
"fill-opacity": chart.theme("guidelineTooltipBackgroundOpacity"),
"stroke": chart.theme("guidelineTooltipBorderColor"),
"stroke-width": chart.theme("guidelineTooltipBorderWidth")
});
chart.svg.group({}, function () {
brush.target.forEach((key, i) => {
let text = chart.svg.text({
"font-size": chart.theme("guidelineTooltipFontSize")
});
text.append(chart.svg.tspan({ "text-anchor": "start", "font-weight": "bold", x : cp * 1.5 }));
text.append(chart.svg.tspan({ "text-anchor": "end" }));
});
}).translate(cp, cp);
chart.svg.group({}, function() {
brush.target.forEach((key, i) => {
chart.svg.circle({
r: chart.theme("guidelineTooltipPointRadius")
});
});
}).translate(cp * 1.5, 0);
});
}).translate(pl, pt);
}
this.drawGuildLine = function(left, value) {
if(line) {
line.attr({
x1: left,
x2: left
});
}
if(xTooltip) {
xTooltip.translate(left - (tw / 2), axis.area("height") + ta);
const message = widget.xFormat.call(self.chart, value);
printXAxisTooltip(1, xTooltip.get(1), message);
}
}
this.drawContentTooltip = function(left, data) {
if(contentTooltip == null || data == null) return;
const cacheTargets = chart.getCache("legend_target", brush.target);
const rect = contentTooltip.children[0];
const texts = contentTooltip.children[1];
let width = 0;
let height = (chart.theme("guidelineTooltipFontSize") * 1.2) * (cacheTargets.length + 1);
let current = 0;
brush.target.forEach((target, index) => {
const targetIndex = cacheTargets.indexOf(target);
const text = contentTooltip.get(1).get(index);
const point = contentTooltip.get(2).get(index);
if(targetIndex != -1) {
// 툴팁 그리기
const y = (chart.theme("guidelineTooltipFontSize") * 1.2) * (targetIndex + 1);
text.attr({ fill: chart.theme("guidelineTooltipFontColor"), y: y });
point.attr({ fill: chart.color(index), cy: y });
points[target].attr({ fill: chart.color(index) });
// 포인트 그리기
current = (widget.stackPoint) ? current + data[target] : data[target];
points[target].translate(left, axis.y(current));
} else {
text.attr({ fill: 'transparent' });
point.attr({ fill: 'transparent' });
points[target].attr({ fill: 'transparent' });
}
});
brush.target.forEach((key, index) => {
if(_.typeCheck("function", widget.tooltipFormat)) {
let ret = widget.tooltipFormat.apply(this, [ data, key ]);
width = Math.max(width, getTextWidth(`${ret.key} ${ret.value}`,
`bold ${chart.theme("guidelineTooltipFontSize")}px ${chart.theme("fontFamily")}`));
texts.get(index).get(0).text(ret.key);
texts.get(index).get(1).text(ret.value);
}
});
rect.attr({
width: width + cp,
height: height
});
for(let i = 0; i < texts.children.length; i++) {
texts.children[i].get(1).attr({ x: width - cp });
}
contentTooltip.translate(
left + width > axis.area("width") ? left - width - cp - lrp : left + lrp,
axis.area("height")/2 - height/2
);
}
this.draw = function() {
const self = this;
chart.on("guideline.show", function(time) {
if(axis.data.length == 0) return;
g.attr({ visibility: "visible" });
const domain = axis.get('x').domain;
const range = +domain[1] - +domain[0];
const interval = range / axis.data.length;
const index = Math.floor((+time - +domain[0]) / interval);
const left = axis.x(index)
self.drawGuildLine(left, time);
self.drawContentTooltip(left, axis.data[index]);
chart.setCache("guideline_time", time);
});
chart.on("guideline.hide", function() {
if(axis.data.length == 0) return;
g.attr({ visibility: "hidden" });
chart.setCache("guideline_time", null);
});
chart.on("render", function() {
const time = chart.getCache("guideline_time", null);
if(time != null)
chart.emit("guideline.show", time);
});
this.on("axis.mouseout", function() {
chart.emit('guideline.hide');
chart.emit('guideline.active');
}, widget.axis);
this.on("axis.mousemove", function(e) {
const time = axis.x.invert(e.chartX);
if(time != chart.getCache("guideline_time", null)) {
chart.emit("guideline.show", time);
chart.emit('guideline.active', time);
}
}, widget.axis);
return g;
}
}
GuideLineWidget.setup = function() {
return {
brush: 0,
xFormat: null,
tooltipFormat: null,
stackPoint: false
};
}
return GuideLineWidget;
}
}