Skip to content

Commit 3676938

Browse files
authored
Enables the label content to be an Image. (#330)
* temporary commit * Enables image as content, updating types, test case and readme. * adds test case for resizeRatio * Removes image because not clear if there is a copyright * implements width and height, adding test case * updates README and types * renames function to get image width and height * removes regex in favor to string parsing. * removes test case because not able to load the image * removes single return to be more readable
1 parent e19879f commit 3676938

File tree

5 files changed

+270
-3
lines changed

5 files changed

+270
-3
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,18 @@ To configure the annotations plugin, you can simply add new config options to yo
159159
// Whether the label is enabled and should be displayed, default is false.
160160
enabled: true,
161161

162-
// Text to display in label - default is null. Provide an array to display values on a new line.
162+
// Content to display in label - default is null. It could be a text, an array of text or an image. Provide an array to display texts on a new line.
163163
content: "Test label",
164+
165+
// Overrides the width of the image. Could be set in pixel by a number, or in percentage of current width of image by a string, default is undefined.
166+
// If undefined, uses the width of the image.
167+
// It is used only when the content is an image.
168+
width: '50%',
169+
170+
// Overrides the height of the image. Could be set in pixel by a number, or in percentage of current height of image by a string, default is undefined.
171+
// If undefined, uses the height of the image.
172+
// It is used only when the content is an image.
173+
height: 200,
164174

165175
// Rotation of label, in degrees, or 'auto' to use the degrees of the line, default is 0.
166176
rotation: 90,

samples/imageLabels.html

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<title>Scatter Chart</title>
6+
<script src="../node_modules/chart.js/dist/chart.js"></script>
7+
<script src="../dist/chartjs-plugin-annotation.js"></script>
8+
<style>
9+
canvas {
10+
-moz-user-select: none;
11+
-webkit-user-select: none;
12+
-ms-user-select: none;
13+
}
14+
</style>
15+
</head>
16+
17+
<body>
18+
<div style="width:75%">
19+
<div>
20+
<canvas id="canvas"></canvas>
21+
</div>
22+
</div>
23+
<script>
24+
const randomScalingFactor = function() {
25+
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
26+
};
27+
const randomColor = function(opacity) {
28+
return 'rgba(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + (opacity || '.3') + ')';
29+
};
30+
31+
const htmlText = new Image();
32+
htmlText.src = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="80">' +
33+
'<foreignObject width="100%" height="100%">' +
34+
'<div xmlns="http://www.w3.org/1999/xhtml" style="color: white;">Here is a <strong style="color: black;">HTML</strong> text, used as image, which can be styled as <em style="font-size: 16px;">YOU</em> like!</div>' +
35+
'</foreignObject></svg>';
36+
37+
const chartjs = new Image();
38+
chartjs.src = './img/chartjs-logo.svg';
39+
40+
const scatterChartData = {
41+
datasets: [{
42+
label: 'My First dataset',
43+
data: [{
44+
x: randomScalingFactor(),
45+
y: randomScalingFactor(),
46+
}, {
47+
x: randomScalingFactor(),
48+
y: randomScalingFactor(),
49+
}, {
50+
x: randomScalingFactor(),
51+
y: randomScalingFactor(),
52+
}, {
53+
x: randomScalingFactor(),
54+
y: randomScalingFactor(),
55+
}, {
56+
x: randomScalingFactor(),
57+
y: randomScalingFactor(),
58+
}, {
59+
x: randomScalingFactor(),
60+
y: randomScalingFactor(),
61+
}, {
62+
x: randomScalingFactor(),
63+
y: randomScalingFactor(),
64+
}]
65+
}, {
66+
label: 'My Second dataset',
67+
data: [{
68+
x: randomScalingFactor(),
69+
y: randomScalingFactor(),
70+
}, {
71+
x: randomScalingFactor(),
72+
y: randomScalingFactor(),
73+
}, {
74+
x: randomScalingFactor(),
75+
y: randomScalingFactor(),
76+
}, {
77+
x: randomScalingFactor(),
78+
y: randomScalingFactor(),
79+
}, {
80+
x: randomScalingFactor(),
81+
y: randomScalingFactor(),
82+
}, {
83+
x: randomScalingFactor(),
84+
y: randomScalingFactor(),
85+
}, {
86+
x: randomScalingFactor(),
87+
y: randomScalingFactor(),
88+
}]
89+
}]
90+
};
91+
92+
scatterChartData.datasets.forEach((dataset) => {
93+
dataset.borderColor = randomColor(0.4);
94+
dataset.backgroundColor = randomColor(0.1);
95+
dataset.pointBorderColor = randomColor(0.7);
96+
dataset.pointBackgroundColor = randomColor(0.5);
97+
dataset.pointBorderWidth = 1;
98+
});
99+
100+
window.onload = function() {
101+
const ctx = document.getElementById('canvas').getContext('2d');
102+
window.myScatter = new Chart(ctx, {
103+
type: 'scatter',
104+
data: scatterChartData,
105+
options: {
106+
title: {
107+
display: true,
108+
text: 'Chart.js Scatter Chart'
109+
},
110+
scales: {
111+
x: {
112+
position: 'top',
113+
gridLines: {
114+
zeroLineColor: 'rgba(0,255,0,1)'
115+
},
116+
scaleLabel: {
117+
display: true,
118+
labelString: 'x axis'
119+
},
120+
ticks: {
121+
maxRotation: 0,
122+
reverse: true,
123+
// min: -100,
124+
// max: 0
125+
}
126+
},
127+
y: {
128+
position: 'right',
129+
gridLines: {
130+
zeroLineColor: 'rgba(0,255,0,1)'
131+
},
132+
scaleLabel: {
133+
display: true,
134+
labelString: 'y axis'
135+
},
136+
ticks: {
137+
reverse: true,
138+
// min: -100,
139+
// max: 0
140+
}
141+
}
142+
},
143+
plugins: {
144+
annotation: {
145+
annotations: {
146+
htmltext: {
147+
type: 'line',
148+
scaleID: 'y',
149+
value: 25,
150+
borderColor: 'red',
151+
borderWidth: 5,
152+
label: {
153+
position: 'start',
154+
backgroundColor: 'red',
155+
content: htmlText,
156+
enabled: true
157+
},
158+
click(context) {
159+
// The annotation is is bound to the `this` variable
160+
console.log('Annotation', context);
161+
}
162+
},
163+
chartjsPercentage: {
164+
type: 'line',
165+
scaleID: 'x',
166+
value: 25,
167+
borderColor: 'rgba(0, 0, 0, 0.2)',
168+
borderWidth: 5,
169+
label: {
170+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
171+
rotation: -45,
172+
position: 'start',
173+
content: chartjs,
174+
enabled: true,
175+
width: '51.10%',
176+
height: '52.8%'
177+
},
178+
click(context) {
179+
// The annotation is is bound to the `this` variable
180+
console.log('Annotation', context);
181+
}
182+
},
183+
chartjsPixel: {
184+
type: 'line',
185+
scaleID: 'x',
186+
value: -30,
187+
borderColor: 'rgba(0, 0, 0, 0.2)',
188+
borderWidth: 5,
189+
label: {
190+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
191+
rotation: -45,
192+
position: 'end',
193+
content: chartjs,
194+
enabled: true,
195+
width: 150,
196+
height: 100
197+
},
198+
click(context) {
199+
// The annotation is is bound to the `this` variable
200+
console.log('Annotation', context);
201+
}
202+
}
203+
}
204+
}
205+
}
206+
}
207+
});
208+
};
209+
</script>
210+
</body>
211+
212+
</html>

samples/img/chartjs-logo.svg

Lines changed: 14 additions & 0 deletions
Loading

src/types/line.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const PI = Math.PI;
66
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
77
const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x;
88
const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y;
9+
const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100;
910

1011
export default class LineAnnotation extends Element {
1112
intersects(x, y, epsilon) {
@@ -163,7 +164,6 @@ function drawLabel(ctx, line, chartArea) {
163164
ctx.textAlign = 'center';
164165

165166
const {width, height} = measureLabel(ctx, label);
166-
167167
const rect = line.labelRect = calculateLabelPosition(line, width, height, chartArea);
168168

169169
ctx.translate(rect.x, rect.y);
@@ -185,15 +185,34 @@ function drawLabel(ctx, line, chartArea) {
185185
);
186186
textYPosition += label.font.size + label.yPadding;
187187
}
188+
} else if (label.content instanceof Image) {
189+
const x = -(width / 2) + label.xPadding;
190+
const y = -(height / 2) + label.yPadding;
191+
ctx.drawImage(label.content, x, y, width - (2 * label.xPadding), height - (2 * label.yPadding));
188192
} else {
189193
ctx.textBaseline = 'middle';
190194
ctx.fillText(label.content, 0, 0);
191195
}
192196
}
193197

198+
function getImageSize(size, value) {
199+
if (typeof value === 'number') {
200+
return value;
201+
} else if (typeof value === 'string') {
202+
return toPercent(value) * size;
203+
}
204+
return size;
205+
}
206+
194207
const widthCache = new Map();
195208
function measureLabel(ctx, label) {
196209
const content = label.content;
210+
if (content instanceof Image) {
211+
return {
212+
width: getImageSize(content.width, label.width) + 2 * label.xPadding,
213+
height: getImageSize(content.height, label.height) + 2 * label.yPadding
214+
};
215+
}
197216
const lines = isArray(content) ? content : [content];
198217
const count = lines.length;
199218
let width = 0;

types/label.d.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,19 @@ export interface LabelOptions {
5353
/**
5454
* Text to display in label. Provide an array to display multiple lines
5555
*/
56-
content: string | string[] | null,
56+
content: string | string[] | HTMLImageElement | null,
57+
58+
/**
59+
* Overrides the width of the image. Could be set in pixel by a number,
60+
* or in percentage of current width of image by a string
61+
*/
62+
width: number | string,
63+
64+
/**
65+
* Overrides the height of the image. Could be set in pixel by a number,
66+
* or in percentage of current height of image by a string
67+
*/
68+
height: number | string,
5769

5870
/**
5971
* Rotation of label, in degrees, or 'auto' to use the degrees of the line, default is 0

0 commit comments

Comments
 (0)