chartjs-plugin-trendline 2.1.7 → 2.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
-
(()=>{var e={339:(e,t)=>{const i={id:"chartjs-plugin-trendline",afterDatasetsDraw:e=>{const t=e.ctx,{xScale:i,yScale:
|
|
1
|
+
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
+
(()=>{var e={339:(e,t)=>{const i={id:"chartjs-plugin-trendline",afterDatasetsDraw:e=>{const t=e.ctx,{xScale:i,yScale:s}=n(e);e.data.datasets.map(((e,t)=>({dataset:e,index:t}))).filter((e=>e.dataset.trendlineLinear)).sort(((e,t)=>{const i=e.dataset.order??0,n=t.dataset.order??0;return 0===i&&0!==n?1:0===n&&0!==i?-1:i-n})).forEach((({dataset:n,index:r})=>{if((n.alwaysShowTrendline||e.isDatasetVisible(r))&&n.data.length>1){const o=e.getDatasetMeta(r);a(o,t,n,i,s)}})),t.setLineDash([])},beforeInit:e=>{e.data.datasets.forEach((t=>{if(t.trendlineLinear&&t.trendlineLinear.label){const i=t.trendlineLinear.label,n=e.legend.options.labels.generateLabels;e.legend.options.labels.generateLabels=function(e){const a=n(e),s=t.trendlineLinear.legend;return s&&!1!==s.display&&a.push({text:s.text||i+" (Trendline)",strokeStyle:s.color||t.borderColor||"rgba(169,169,169, .6)",fillStyle:s.fillStyle||"transparent",lineCap:s.lineCap||"butt",lineDash:s.lineDash||[],lineWidth:s.width||1}),a}}}))}},n=e=>{let t,i;for(const n of Object.values(e.scales))if(n.isHorizontal()?t=n:i=n,t&&i)break;return{xScale:t,yScale:i}},a=(e,t,i,n,a)=>{const x=i.yAxisID||"y",h=e.controller.chart.scales[x]||a,u=i.borderColor||"rgba(169,169,169, .6)",{colorMin:y=u,colorMax:f=u,width:m=i.borderWidth||3,lineStyle:p="solid",fillColor:b=!1}=i.trendlineLinear||{},{color:w=u,text:g="Trendline",display:F=!0,displayValue:L=!0,offset:S=10,percentage:v=!1}=i.trendlineLinear.label||{},{family:C="'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:D=12}=i.trendlineLinear.label?.font||{},T=e.controller.chart.options,N="object"==typeof T.parsing?T.parsing:void 0,P=i.trendlineLinear?.xAxisKey||N?.xAxisKey||"x",A=i.trendlineLinear?.yAxisKey||N?.yAxisKey||"y";let M=new c,V=i.data.findIndex((e=>null!=e)),k=i.data.length-1,j=e.data[V]?.[P],E=e.data[k]?.[P],W="object"==typeof i.data[V];i.data.forEach(((e,t)=>{if(null!=e)if(["time","timeseries"].includes(n.options.type)){let i=null!=e[P]?e[P]:e.t;void 0!==i?M.add(new Date(i).getTime(),e[A]):M.add(t,e)}else W?isNaN(e.x)||isNaN(e.y)?isNaN(e.x)?isNaN(e.y)||M.add(t,e.y):M.add(t,e.x):M.add(e.x,e.y):M.add(t,e)}));let I,K,$=isFinite(j)?j:n.getPixelForValue(M.minx),B=h.getPixelForValue(M.f(M.minx));if(i.trendlineLinear.projection&&M.scale()<0){let e=M.fo();e<M.minx&&(e=M.maxx),I=n.getPixelForValue(e),K=h.getPixelForValue(M.f(e))}else I=isFinite(E)?E:n.getPixelForValue(M.maxx),K=h.getPixelForValue(M.f(M.maxx));const H=e.controller.chart.chartArea.bottom,z=e.controller.chart.width;if(isFinite($)&&isFinite(B)&&isFinite(I)&&isFinite(K)){r({x1:$,y1:B,x2:I,y2:K,drawBottom:H,chartWidth:z}),t.lineWidth=m,o(t,p),l({ctx:t,x1:$,y1:B,x2:I,y2:K,colorMin:y,colorMax:f}),b&&d(t,$,B,I,K,H,b);const e=Math.atan2(K-B,I-$),n=(B-K)/(I-$);if(i.trendlineLinear.label&&!1!==F){const i=L?`${g} (Slope: ${v?(100*n).toFixed(2)+"%":n.toFixed(2)})`:g;s(t,i,$,B,I,K,e,w,C,D,S)}}},s=(e,t,i,n,a,s,r,o,l,d,c)=>{e.font=`${d}px ${l}`,e.fillStyle=o;const x=e.measureText(t).width,h=(i+a)/2,u=(n+s)/2;e.save(),e.translate(h,u),e.rotate(r);const y=-x/2,f=c;e.fillText(t,y,f),e.restore()},r=({x1:e,y1:t,x2:i,y2:n,drawBottom:a,chartWidth:s})=>{if(t>a){t=a}else if(n>a){let e=n-a,r=n-t;n=a,i=s-(i-(s-s*(e/r)))}},o=(e,t)=>{switch(t){case"dotted":e.setLineDash([2,2]);break;case"dashed":e.setLineDash([8,3]);break;case"dashdot":e.setLineDash([8,3,2,3]);break;default:e.setLineDash([])}},l=({ctx:e,x1:t,y1:i,x2:n,y2:a,colorMin:s,colorMax:r})=>{if(isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(a)){e.beginPath(),e.moveTo(t,i),e.lineTo(n,a);try{let o=e.createLinearGradient(t,i,n,a);o.addColorStop(0,s),o.addColorStop(1,r),e.strokeStyle=o}catch(t){console.warn("Gradient creation failed, using solid color:",t),e.strokeStyle=s}e.stroke(),e.closePath()}else console.warn("Cannot draw trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:a})},d=(e,t,i,n,a,s,r)=>{isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(a)&&isFinite(s)?(e.beginPath(),e.moveTo(t,i),e.lineTo(n,a),e.lineTo(n,s),e.lineTo(t,s),e.lineTo(t,i),e.closePath(),e.fillStyle=r,e.fill()):console.warn("Cannot fill below trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:a,drawBottom:s})};class c{constructor(){this.count=0,this.sumx=0,this.sumy=0,this.sumx2=0,this.sumxy=0,this.minx=Number.MAX_VALUE,this.maxx=Number.MIN_VALUE}add(e,t){this.sumx+=e,this.sumy+=t,this.sumx2+=e*e,this.sumxy+=e*t,e<this.minx&&(this.minx=e),e>this.maxx&&(this.maxx=e),this.count++}slope(){const e=this.count*this.sumx2-this.sumx*this.sumx;return(this.count*this.sumxy-this.sumx*this.sumy)/e}intercept(){return(this.sumy-this.slope()*this.sumx)/this.count}f(e){return this.slope()*e+this.intercept()}fo(){return-this.intercept()/this.slope()}scale(){return this.slope()}}"undefined"!=typeof window&&window.Chart&&(window.Chart.hasOwnProperty("register")?window.Chart.register(i):window.Chart.plugins.register(i));try{e.exports=i}catch(e){}}},t={};!function i(n){var a=t[n];if(void 0!==a)return a.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,i),s.exports}(339)})();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
4
|
-
*
|
|
5
|
-
* Copyright 2025 Marcus Alsterfjord
|
|
6
|
-
* Released under the MIT license
|
|
7
|
-
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
|
|
8
|
-
*
|
|
9
|
-
* Modified by @vesal: accept xy-data from scatter,
|
|
10
|
-
* Modified by @Megaemce: add label and basic legend to trendline, add JSDoc,
|
|
11
|
-
*/
|
|
1
|
+
/*!
|
|
2
|
+
* chartjs-plugin-trendline.js
|
|
3
|
+
* Version: 2.1.9
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2025 Marcus Alsterfjord
|
|
6
|
+
* Released under the MIT license
|
|
7
|
+
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
|
|
8
|
+
*
|
|
9
|
+
* Modified by @vesal: accept xy-data from scatter,
|
|
10
|
+
* Modified by @Megaemce: add label and basic legend to trendline, add JSDoc,
|
|
11
|
+
*/
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Chart.js Example</title>
|
|
7
|
+
<!-- Include Chart.js and the Trendline plugin -->
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
10
|
+
<script src="../src/chartjs-plugin-trendline.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<!-- Canvas element for rendering the chart -->
|
|
15
|
+
<canvas id="chartjs" width="400" height="200"></canvas>
|
|
16
|
+
|
|
17
|
+
<script type="text/javascript">
|
|
18
|
+
// Define colors
|
|
19
|
+
const barColor1 = '#258cda'; // Blue color for incidents
|
|
20
|
+
const borderColor1 = '#258cda'; // Same as backgroundColor for incidents
|
|
21
|
+
const barColor2 = '#cccccc'; // Gray color for outage
|
|
22
|
+
const borderColor2 = '#808080'; // Darker gray for borderColor of outage
|
|
23
|
+
|
|
24
|
+
// Get the context of the canvas element we want to select
|
|
25
|
+
const ctx = document.getElementById("chartjs").getContext('2d');
|
|
26
|
+
|
|
27
|
+
const chartDynamics = [
|
|
28
|
+
{
|
|
29
|
+
datepost: "2025-03-01T00:00:00",
|
|
30
|
+
quantity: 75,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
datepost: "2025-03-02T00:00:00",
|
|
34
|
+
quantity: 64,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
datepost: "2025-03-03T00:00:00",
|
|
38
|
+
quantity: 52,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
datepost: "2025-03-04T00:00:00",
|
|
42
|
+
quantity: 23,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
datepost: "2025-03-05T00:00:00",
|
|
46
|
+
quantity: 44,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
datepost: "2025-03-06T00:00:00",
|
|
50
|
+
quantity: 38,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
datepost: "2025-03-07T00:00:00",
|
|
54
|
+
quantity: 44,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
datepost: "2025-03-08T00:00:00",
|
|
58
|
+
quantity: 41,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
datepost: "2025-03-09T00:00:00",
|
|
62
|
+
quantity: 78,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
datepost: "2025-03-10T00:00:00",
|
|
66
|
+
quantity: 31,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Create a new Chart instance
|
|
71
|
+
const myChart1 = new Chart(ctx, {
|
|
72
|
+
type: "line",
|
|
73
|
+
data: {
|
|
74
|
+
labels: chartDynamics.map((o) => o.datepost),
|
|
75
|
+
datasets: [
|
|
76
|
+
{
|
|
77
|
+
label: "total",
|
|
78
|
+
data: chartDynamics.map((o) => o.quantity),
|
|
79
|
+
borderWidth: 1,
|
|
80
|
+
pointStyle: false,
|
|
81
|
+
trendlineLinear: { lineStyle: "dotted", width: 2 },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
options: {
|
|
86
|
+
maintainAspectRatio: false,
|
|
87
|
+
scales: {
|
|
88
|
+
x: { type: "time", time: { tooltipFormat: "d", minUnit: "day" } },
|
|
89
|
+
},
|
|
90
|
+
interaction: { mode: "nearest", intersect: false },
|
|
91
|
+
plugins: {
|
|
92
|
+
legend: { display: false },
|
|
93
|
+
autocolors: { enabled: false },
|
|
94
|
+
datalabels: { display: false },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
3
|
+
* Version: 2.1.9
|
|
4
4
|
*
|
|
5
5
|
* Copyright 2025 Marcus Alsterfjord
|
|
6
6
|
* Released under the MIT license
|
|
@@ -25,16 +25,27 @@ const pluginTrendlineLinear = {
|
|
|
25
25
|
const ctx = chartInstance.ctx;
|
|
26
26
|
const { xScale, yScale } = getScales(chartInstance);
|
|
27
27
|
|
|
28
|
-
chartInstance.data.datasets
|
|
28
|
+
const sortedDatasets = chartInstance.data.datasets
|
|
29
|
+
.map((dataset, index) => ({ dataset, index }))
|
|
30
|
+
.filter((entry) => entry.dataset.trendlineLinear)
|
|
31
|
+
.sort((a, b) => {
|
|
32
|
+
const orderA = a.dataset.order ?? 0;
|
|
33
|
+
const orderB = b.dataset.order ?? 0;
|
|
34
|
+
|
|
35
|
+
// Push 0-order datasets to the end (they draw last / on top)
|
|
36
|
+
if (orderA === 0 && orderB !== 0) return 1;
|
|
37
|
+
if (orderB === 0 && orderA !== 0) return -1;
|
|
38
|
+
|
|
39
|
+
// Otherwise, draw lower order first
|
|
40
|
+
return orderA - orderB;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
sortedDatasets.forEach(({ dataset, index }) => {
|
|
29
44
|
const showTrendline =
|
|
30
45
|
dataset.alwaysShowTrendline ||
|
|
31
46
|
chartInstance.isDatasetVisible(index);
|
|
32
47
|
|
|
33
|
-
if (
|
|
34
|
-
dataset.trendlineLinear &&
|
|
35
|
-
showTrendline &&
|
|
36
|
-
dataset.data.length > 1
|
|
37
|
-
) {
|
|
48
|
+
if (showTrendline && dataset.data.length > 1) {
|
|
38
49
|
const datasetMeta = chartInstance.getDatasetMeta(index);
|
|
39
50
|
addFitter(datasetMeta, ctx, dataset, xScale, yScale);
|
|
40
51
|
}
|
|
@@ -148,6 +159,8 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
148
159
|
(d) => d !== undefined && d !== null
|
|
149
160
|
);
|
|
150
161
|
let lastIndex = dataset.data.length - 1;
|
|
162
|
+
let startPos = datasetMeta.data[firstIndex]?.[xAxisKey];
|
|
163
|
+
let endPos = datasetMeta.data[lastIndex]?.[xAxisKey];
|
|
151
164
|
let xy = typeof dataset.data[firstIndex] === 'object';
|
|
152
165
|
|
|
153
166
|
// Collect data points for the fitter
|
|
@@ -175,7 +188,9 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
175
188
|
});
|
|
176
189
|
|
|
177
190
|
// Calculate the pixel coordinates for the trendline
|
|
178
|
-
let x1 =
|
|
191
|
+
let x1 = isFinite(startPos)
|
|
192
|
+
? startPos
|
|
193
|
+
: xScale.getPixelForValue(fitter.minx);
|
|
179
194
|
let y1 = yScaleToUse.getPixelForValue(fitter.f(fitter.minx));
|
|
180
195
|
let x2, y2;
|
|
181
196
|
|
|
@@ -186,13 +201,10 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
186
201
|
x2 = xScale.getPixelForValue(x2value);
|
|
187
202
|
y2 = yScaleToUse.getPixelForValue(fitter.f(x2value));
|
|
188
203
|
} else {
|
|
189
|
-
x2 = xScale.getPixelForValue(fitter.maxx);
|
|
204
|
+
x2 = isFinite(endPos) ? endPos : xScale.getPixelForValue(fitter.maxx);
|
|
190
205
|
y2 = yScaleToUse.getPixelForValue(fitter.f(fitter.maxx));
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
// Do not use startPos and endPos directly, as they may be undefined
|
|
194
|
-
// This was causing the vertical line issue
|
|
195
|
-
|
|
196
208
|
const drawBottom = datasetMeta.controller.chart.chartArea.bottom;
|
|
197
209
|
const chartWidth = datasetMeta.controller.chart.width;
|
|
198
210
|
|
package/example/issue.html
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Chart.js Example</title>
|
|
7
|
-
<!-- Include Chart.js and the Trendline plugin -->
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
-
<script src="../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
|
|
13
|
-
<!-- Canvas element for rendering the chart -->
|
|
14
|
-
<canvas id="chartjs" width="400" height="200"></canvas>
|
|
15
|
-
|
|
16
|
-
<script type="text/javascript">
|
|
17
|
-
// Define colors
|
|
18
|
-
const barColor1 = '#258cda'; // Blue color for incidents
|
|
19
|
-
const borderColor1 = '#258cda'; // Same as backgroundColor for incidents
|
|
20
|
-
const barColor2 = '#cccccc'; // Gray color for outage
|
|
21
|
-
const borderColor2 = '#808080'; // Darker gray for borderColor of outage
|
|
22
|
-
|
|
23
|
-
// Get the context of the canvas element we want to select
|
|
24
|
-
const ctx = document.getElementById("chartjs").getContext('2d');
|
|
25
|
-
|
|
26
|
-
// Create a new Chart instance
|
|
27
|
-
const myChart1 = new Chart(ctx, {
|
|
28
|
-
type: 'bar', // Specify chart type (e.g., bar)
|
|
29
|
-
|
|
30
|
-
data: {
|
|
31
|
-
labels: ["July 2024","August 2024","September 2024","October 2024","November 2024","December 2024","January 2025","February 2025","March 2025","April 2025","May 2025","June 2025"], // Example labels for x-axis
|
|
32
|
-
datasets: [
|
|
33
|
-
{
|
|
34
|
-
label: '# of Incidents',
|
|
35
|
-
yAxisID: 'y1',
|
|
36
|
-
fill: false,
|
|
37
|
-
backgroundColor: barColor1,
|
|
38
|
-
borderColor: borderColor1,
|
|
39
|
-
borderWidth: 3,
|
|
40
|
-
tension: 0.3,
|
|
41
|
-
data: [7,6,13,6,4,3,4,4,0,0,0,0], // Example data for incidents
|
|
42
|
-
trendlineLinear: {
|
|
43
|
-
lineStyle: 'dotted',
|
|
44
|
-
width: 2
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
label: 'Total Outage',
|
|
49
|
-
yAxisID: 'y2',
|
|
50
|
-
fill: true,
|
|
51
|
-
backgroundColor: barColor2,
|
|
52
|
-
borderColor: borderColor2,
|
|
53
|
-
borderWidth: 3,
|
|
54
|
-
tension: 0.3,
|
|
55
|
-
data: [112,201,316,16,16,13,11,81,20,300,250,200], // Example data for outage in hours
|
|
56
|
-
trendlineLinear: {
|
|
57
|
-
lineStyle: 'dotted',
|
|
58
|
-
width: 2
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
options: {
|
|
65
|
-
plugins: {
|
|
66
|
-
legend: {
|
|
67
|
-
position: 'bottom', // Position of the legend
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
tooltips: {
|
|
71
|
-
displayColors: true, // Display color in tooltip
|
|
72
|
-
mode: 'index', // Tooltip mode
|
|
73
|
-
},
|
|
74
|
-
scales: {
|
|
75
|
-
x: { // X-axis configuration
|
|
76
|
-
ticks: {
|
|
77
|
-
autoSkip: true,
|
|
78
|
-
maxTicksLimit: 12,
|
|
79
|
-
maxRotation: 45,
|
|
80
|
-
minRotation: 45,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
y1: { // Left Y-axis for incidents
|
|
84
|
-
ticks: {
|
|
85
|
-
beginAtZero: true,
|
|
86
|
-
},
|
|
87
|
-
title: {
|
|
88
|
-
display: true,
|
|
89
|
-
text: 'Incidents',
|
|
90
|
-
},
|
|
91
|
-
type: 'linear',
|
|
92
|
-
position: 'left',
|
|
93
|
-
},
|
|
94
|
-
y2: { // Right Y-axis for outage in hours
|
|
95
|
-
ticks: {
|
|
96
|
-
beginAtZero: true,
|
|
97
|
-
},
|
|
98
|
-
title: {
|
|
99
|
-
display: true,
|
|
100
|
-
text: 'Outage in Hours',
|
|
101
|
-
},
|
|
102
|
-
type: 'linear',
|
|
103
|
-
position: 'right',
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
responsive: true, // Make the chart responsive
|
|
107
|
-
maintainAspectRatio: true, // Maintain aspect ratio
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
</script>
|
|
111
|
-
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|