ChartJS/ ChartJS-plugin annotation set height of vertical line - chart.js

Is it possible to set the height of a vertical line in chart js ?
For instance this example:
https://jsfiddle.net/caj89x6L/
{
type: 'line',
id: 'vline' + index,
mode: 'vertical',
scaleID: 'x-axis-0',
value: date,
**endValue: 3.5, ??
height: 3.5,** ??
borderColor: 'green',
borderWidth: 1,
label: {
enabled: true,
position: "center",
content: amount[index]
}
}
Can i set somewhere a height property ?
endValue dows not work

Hey I don't know if this still helps you, but I wrote a plugin for ChartJS which does exactly what you're asking. You may be able to adapt the source code in the repo for your own needs. Here's a relevant snippet:
/**
* Draw the line height annotation to the highest data point on the chart.
* #param {int} x horizontal coordinate on canvas
* #param {int} bottomY bottom Y dimension of the chart
* #param {float} highestDataY highest possible Y value on the chart, taking padding and border offsets into consideration.
*/
drawLineHeightAnnotation(x, bottomY, highestDataY) {
let ctx = this.ctx;
let options = this.options;
ctx.save();
ctx.beginPath();
if (!options.noDash) {
ctx.setLineDash([10, 10]);
}
ctx.moveTo(x, highestDataY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = options.lineWeight ? options.lineWeight : 1.5;
ctx.strokeStyle = options.color ? options.color : "#000";
ctx.stroke();
ctx.restore();
}

Related

How to create linear gradient with background mask in Chart.js

I would like to recreate this chart color scheme in Chart.js.
So far I've succeeded in creating the horizontal linear gradient for both the stroke and background colors, but I can't find a way to create the opacity mask for the background color to 'blend' it into the page background.
This is my chart so far
Note:
I can create an opacity mask on the canvas itself using css property:
-webkit-mask-image: -webkit-gradient(linear, left 50%, left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)))
But this method masks the whole lower bottom of the chart, i.e the stroke of the chart, for example
How would I go about masking only the background color of the chart?
Chart.js setup
data = {
labels: labels,
datasets: [
{
label: 'Dataset 1',
fill: true,
data: Utils.numbers(NUMBER_CFG),
borderColor: getGradient,
pointBorderColor: getGradient,
pointBackgroundColor: getGradient,
pointHoverBackgroundColor: getGradient,
pointHoverBorderColor: getGradient,
backgroundColor: getGradient
},
]
};
let width, height, gradient;
function gradient(ctx, chartArea) {
const chartWidth = chartArea.right - chartArea.left;
const chartHeight = chartArea.bottom - chartArea.top;
if (!gradient || width !== chartWidth || height !== chartHeight) {
// Create the gradient because this is either the first render
// or the size of the chart has changed
width = chartWidth;
height = chartHeight;
var gradientStroke = ctx.createLinearGradient(chartArea.right, chartArea.top, chartArea.left, chartArea.top);
gradientStroke.addColorStop(0, "#80b6f4");
gradientStroke.addColorStop(1, "#f49080");
}
return gradientStroke;
}
function getGradient(context) {
const chart = context.chart;
const {ctx, chartArea} = chart;
if (!chartArea) {
// This case happens on initial chart load
return;
}
return gradient(ctx, chartArea);
}
I hope you have solved that by now. In any case, I think you should try to change the values you pass to createLinearGradient function. See this example (it's not mine, but helped me to understand that).
var ctx = document.getElementById("chart").getContext("2d");
/*** Gradient ***/
var gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, 'rgba(250,174,50,1)');
gradient.addColorStop(1, 'rgba(250,174,50,0)');
/***************/
var data = {
labels : ["02:00","04:00","06:00","08:00","10:00","12:00","14:00","16:00","18:00","20:00","22:00","00:00"],
datasets: [
{
fillColor : gradient, // Put the gradient here as a fill color
strokeColor : "#ff6c23",
pointColor : "#fff",
pointStrokeColor : "#ff6c23",
pointHighlightFill: "#fff",
pointHighlightStroke: "#ff6c23",
data : [25.0,32.4,22.2,39.4,34.2,22.0,23.2,24.1,20.0,18.4,19.1,17.4]
}
]
};
var options = {
responsive: true,
datasetStrokeWidth : 3,
pointDotStrokeWidth : 4,
tooltipFillColor: "rgba(0,0,0,0.8)",
tooltipFontStyle: "bold",
tooltipTemplate: "<%if (label){%><%=label + ' hod' %>: <%}%><%= value + '°C' %>",
scaleLabel : "<%= Number(value).toFixed(0).replace('.', ',') + '°C'%>"
};
var myLineChart = new Chart(ctx).Line(data, options);
<canvas id="chart" width="800" height="400"></canvas>

ChartJs: Is there a way to control the font options per line for a multiline axis label

I am open to learning that there is already a way (via configuration, or developing a plugin) to hook into the rendering of the label of an axis, such that I could control aspects of the font used to render each line of a multiline label (e.g., what I need to render would be similar visually to a label and sub-label below it, with the primary label being bolded and a larger font size, while the sub-label directly beneath it would be normal font weight and a smaller size).
I am using ChartJs version 3.5.1 to render a horizontal barchart (meaning that the dataset labels on the left are really configured under the y axis), and have tried a few different things already:
Hooking into the tick callback - but I can't even use this function to duplicate default functionality (the value coming into that function isn't the label text; instead it is the index/ordinal of the data row?). Even if I could get this to work as shown in examples, it appears like this would be more for the content of the label than any of the configuration options themselves.
Setting the font configuration for ticks to be an array - but this only serves to allow me to change the font between data rows (e.g., I can make the label of the top row in my horizontal bar chart be size 22, the second label 10, etc. - but not change font attributes within lines of a given label)
Using a plugin like afterDraw to try to go tweak things - but again, the configuration at that point seems to only consider all of the lines together as one label.
Tried looking through past PRs to the project (mostly centered around adding multiline label support, as well as bug fixes specific to that area) to get any additional insight
If there isn't a way currently (via plugins or existing configuration), does anyone have a good feel for where to start attacking this sort of a change as a new PR?
UPDATE
As was shared as a response to my corresponding ChartJs feature request and as the accepted answer below, a custom plugin seems to be the only way currently to accomplish what I wanted for now.
Here are the key bits from my configuration (admittedly much more "one time use only" than the accepted answer, as I moved some of the configuration inside of the plugin as hard-coded values given my relatively narrow use case):
// this will be passed into the chart constructor...
const options = {
//...
scales: {
//...
// I wanted to impact the lefthand side of a horizontal bar chart
y: {
ticks: {
// make the original labels white for later painting over with custom sub-labels
color: "white",
// we still want this here to be able to take up the same space as the eventual label we will stick here
font: {
size: 22,
weight: "bold"
}
}
},
//...
}
};
// This is my plugin, also later passed into the chart constructor
const customSubLabelsPlugin = {
id: "customSubLabels",
afterDraw: (chart, args, opts) => {
// Set all variables needed
const {
ctx,
// I only cared about altering one specific axis
scales: { y }
} = chart;
const labelItems = y._labelItems;
const fontStringSubTitle = "16px Helvetica,Arial,sans-serif";
const fontStringMain = "bold 22px Helvetica,Arial,sans-serif";
// loop over each dataset label
for (let i = 0; i < labelItems.length; i++) {
let labelItem = labelItems[i];
// For purposes of redrawing, we are going to always assume that each label is an array - because we make it that way if we need to
const label = Array.isArray(labelItem.label)
? labelItem.label
: [labelItem.label];
// Draw new text on canvas
let offset = 0;
label.forEach((el) => {
let elTextMetrics = ctx.measureText(el);
if (labelItem.label.indexOf(el) === 0) {
ctx.font = fontStringMain;
} else {
ctx.font = fontStringSubTitle;
}
ctx.save();
ctx.fillStyle = "#546a6f";
ctx.fillText(
el,
labelItem.translation[0],
labelItem.translation[1] + labelItem.textOffset + offset
);
ctx.restore();
offset +=
elTextMetrics.fontBoundingBoxAscent +
elTextMetrics.fontBoundingBoxDescent;
});
}
}
};
You can use a plugin to redraw the ticks for you, might need some finetuning for your specific needs:
var options = {
type: 'line',
data: {
labels: [
["Red", "subTitle"],
["Blue", "subTitle"],
["Yellow", "subTitle"],
["Green", "subTitle"],
["Purple", "subTitle"],
["Orange", "subTitle"]
],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'red',
backgroundColor: 'red'
}]
},
options: {
plugins: {
customTextColor: {
color: 'blue',
boxColor: 'white',
fontStringSubTitle: 'italic 12px Comic Sans MS',
fontStringMain: ''
}
}
},
plugins: [{
id: 'customTextColor',
afterDraw: (chart, args, opts) => {
// Set all variables needed
const {
ctx,
scales: {
y,
x
}
} = chart;
const labelItems = x._labelItems;
const {
color,
boxColor,
fontStringMain,
fontStringSubTitle
} = opts;
const defaultFontString = '12px "Helvetica Neue", Helvetica, Arial, sans-serif';
for (let i = 0; i < labelItems.length; i++) {
let labelItem = labelItems[i];
if (!Array.isArray(labelItem.label)) {
continue;
}
let metrics = ctx.measureText(labelItem.label);
let labelWidth = metrics.width;
let labelHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
//Draw box over old labels so they are inviseble
ctx.save();
ctx.fillStyle = boxColor || '#FFFFFF';
ctx.fillRect((labelItem.translation[0] - labelWidth / 2), labelItem.translation[1], labelWidth, labelHeight * labelItem.label.length);
ctx.restore();
// Draw new text on canvas
let offset = 0;
labelItem.label.forEach(el => {
let elTextMetrics = ctx.measureText(el);
let elWidth = elTextMetrics.width;
if (labelItem.label.indexOf(el) === 0) {
ctx.font = fontStringMain || defaultFontString;
} else {
ctx.font = fontStringSubTitle || defaultFontString;
}
ctx.save();
ctx.fillStyle = color || Chart.defaults.color
ctx.fillText(el, (labelItem.translation[0] - elWidth / 2), labelItem.translation[1] + labelItem.textOffset + offset);
ctx.restore();
offset += elTextMetrics.fontBoundingBoxAscent + elTextMetrics.fontBoundingBoxDescent;
});
}
// Draw white box over old label
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.js"></script>
</body>

How to style a pie chart in chart js? I want to change the border color, border width and give them shadow

Here is my HTML div:
<canvas id="mycanvas" width="290" height="140"></canvas>
JavaScript:
$(document).ready(function() {
var ctx = $("#mycanvas").get(0).getContext("2d");
var data = [{
value: 923864,
color: "#58508d",
highlight: "#003f5c",
label: "Dr. John",
},
{
value: 720988,
color: "#3292b0",
highlight: "#6fefff",
label: "Alex"
},
{
value: 179539,
color: "orange",
highlight: "darkblue",
label: "Other",
},
];
var piechart = new Chart(ctx).Pie(data);
});
First at all, you should upgrade to the most stable version of Chart.js, which currently is v2.9.4.
The dataset accepts number of properties, that can be defined for styling the border. Its color and width are controlled through the following ones.
borderColor arc border color
borderWidth arc border width (in pixels).
In order to see a shadow, you can use the Plugin Core API. The API offers a range of hooks that may be used for performing custom code. In the beforeDraw for example, you can draw a circle with shadow through CanvasRenderingContext2D.arc().
Please take a look at your amended code below and see how it works.
new Chart('canvas', {
type: 'pie',
plugins: [{
beforeDraw: chart => {
var ctx = chart.chart.ctx;
ctx.save();
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.shadowColor = 'black';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = -10;
ctx.shadowOffsetY = 0;
const x = chart.chart.width / 2;
const y = chart.chart.height / 2 + 15;
ctx.arc(x, y, 95, 0, Math.PI*2, false);
ctx.fill();
ctx.restore();
}
}],
data: {
labels: ['Dr. John', 'Alex', 'Other'],
datasets: [{
data: [923864, 720988, 179539],
backgroundColor: ['#58508d', '#3292b0', 'orange'],
hoverBackgroundColor: ['#003f5c', '#6fefff', 'darkblue'],
borderWidth: 0
}],
},
options: {
responsive: false,
layout: {
padding: {
top: 15,
bottom: 20
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="canvas" height="260"></canvas>

How to add image to chart.js tooltip?

i'm using Chart.js to build a line graph by specific directions from a designer, and I want my tooltip to include a small icon.
is it possible ?
You can override the customTooltips function.
var myLineChart = new Chart(ctx).Line(data, {
customTooltips: function (tooltip) {
var tooltipEl = $('#chartjs-tooltip');
if (!tooltip) {
tooltipEl.css({
opacity: 0
});
return;
}
tooltipEl.removeClass('above below');
tooltipEl.addClass(tooltip.yAlign);
// split out the label and value and make your own tooltip here
var parts = tooltip.text.split(":");
var innerHtml = '<img src="pathTomyImage/myImage.png"> <span>' + parts[0].trim() + '</span> : <span><b>' + parts[1].trim() + '</b></span>';
tooltipEl.html(innerHtml);
tooltipEl.css({
opacity: 1,
left: tooltip.chart.canvas.offsetLeft + tooltip.x + 'px',
top: tooltip.chart.canvas.offsetTop + tooltip.y + 'px',
fontFamily: tooltip.fontFamily,
fontSize: tooltip.fontSize,
fontStyle: tooltip.fontStyle,
});
}
});
Replace pathTomyImage/myImage.png with your image URL (you could also pick this from a lookup using parts[0] - which is the x axis label, or easier still give your images a name depending on the axis label. eg. January.png, February.png)
Make sure you add the following markup as well
<div id="chartjs-tooltip"></div>
Fiddle - http://jsfiddle.net/02xrgy10/

famo.us - RenderController and draggable surfaces

I have two surfaces. One is draggable and one is 'full screen' (size: [undefined, undefined]). I'd like to drag the first surface (yellow in my example) right and have the second (green)surface appear. When I click on the green surface, I'd like show the first surface again back in the original starting point (center of screen).
I'd also like the green surface to be non-draggable.
I'm a famo.us novice, and any help is GREATLY appreciated!
fiddle here: http://jsfiddle.net/cjs123456/vfzy4j51/
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function(data){
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.show(fullSurface);
}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform : function(){
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin : [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function() {
renderController.show(mySurface);
});
renderController.show(mySurface);
var mainContext = Engine.createContext();
var node = mainContext.add(centerModifier).add(positionModifier);
node.add(renderController);
});
There are more than one way to solve this issue.
Control the dragging of the render controller
Create separate render controller and control views
Because your use case here is simple enough, I will show the creating of a separate render controller
Here is the jsFiddle Example of the Code
Create a background render controller
var renderController = new RenderController();
var backRenderController = new RenderController();
Change the node to be the center modifier. Add our draggable to the node. Add the background controller to the node.
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);
Control the draggable render contoller view (Line 42)
renderController.hide();
backRenderController.show(fullSurface);
Clicking on the background will reset the position of the draggable back to the origin, hide the background and show the draggable again.
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
Full code:
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var backRenderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function (data) {
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.hide();
backRenderController.show(fullSurface);
}
//else {
// mySurface.setPosition([0,0,0], {
// curve: Easing.outBack,
// duration: 300
// });
//}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform: function () {
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin: [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function () {
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
});
//var node = mainContext.add(myModifier);
//node.add(draggable).add(surface);
renderController.show(mySurface);
var mainContext = Engine.createContext();
//mainContext.add(myModifier).add(draggable).add(renderController);
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);