I’m wondering if there is a way to distinguish the data point being inside or outside of a bar.
Basically, with the chart below
I want the text 9178 to be in a colour I generate (because it’s inside a bar)
I want the rest of the numbers to be black (because they are outside of bars)
I’m wondering if the API provides something that I can use programmatically to decide what colour to use.
e.g. If a data point has a method isOverflow() that I can call, then I can set the colour to black if it returns true.
created on jsfiddle: https://jsfiddle.net/Mingzilla/pjqhqn4u/6
var generateColor = function(item) {
var lightColor = "#DADADA";
var color = new Highcharts.Color(item.point.color).rgba;
var isLightBg = color[0] + color[1] + color[2] > 384;
return isLightBg ? '#000000' : lightColor;
};
$(function() {
$('#container').highcharts({
title: {
text: 'I want data point inside a bar to be my colour, and outside to be black'
},
chart: {
type: 'column'
},
xAxis: {
categories: ['Car Insurance',
'Life Insurance',
'Pet Insurance'
]
},
plotOptions: {
series: {
dataLabels: {
enabled: true,
overflow: 'justify',
style: {
fontWeight: 'normal',
textShadow: 'none'
},
formatter: function() {
var item = this;
return '<span style="fill:' +
generateColor(item) + '">' +
(item.point.formattedValue || item.point.y) +
'</span>';
}
}
}
},
series: [{
"color": "#666699",
"name": "North",
"data": [{
"color": "#666699",
"name": "Car Insurance",
"y": 9178
}, {
"color": "#666699",
"name": "Life Insurance",
"y": 4518
}, {
"color": "#666699",
"name": "Pet Insurance",
"y": 1450
}]
}, {
"color": "#663366",
"name": "South",
"data": [{
"color": "#663366",
"name": "Car Insurance",
"y": 2129
}, {
"color": "#663366",
"name": "Life Insurance",
"y": 1066
}, {
"color": "#663366",
"name": "Pet Insurance",
"y": 374
}]
}]
});
});
Defaulty the color is based on contrast or declared single color. But you can return a color (i.e black) for all datalabels. Then catch load / redraw events in the chart object to interate on all dataLabels. Inside each of them, there is information about position and verticalAlign, which can be used to check where label is. If this is top, apply your lightColor by css() method.
var lightColor = "#DADADA";
function datalabelColor() {
var chart = this,
series = chart.series,
each = Highcharts.each,
dL;
each(series, function(serie, i) {
each(serie.data, function(p, j) {
dL = p.dataLabel;
if(dL.alignOptions.verticalAlign === 'top') {
dL.css({
color: lightColor
});
}
});
});
}
Example:
- https://jsfiddle.net/mys5k9ty/
Let me know if you have any further questions.
Related
Chart.js scatter chart only accepts data in a point format (x, y). Im a trying to fill the data points with infomration about medications from a file called meds.json
More specifically, the x would be the month of last fill date of the medication and the y would be the dose.
How can I grab all this data from the meds.json file and insert it to the data to create the points for my scatter plot?
If I try to grab all dates and store them in an array, and all the dose values in another array, how can I use that populate the point data using those arrays?
Here's how to make a scatter plot using Chart.js, I am trying to populate the x,y points under data 'data':
// charts.js
var scatterChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: [{
// x = month, y = dose
// fill these in for all meds in the json file
x: -10,
y: 0
}, {
x: 0,
y: 10
}, {
x: 10,
y: 5
}]
}]
},
options: {
scales: {
xAxes: [{
type: 'linear',
position: 'top'
}]
}
}
});
}
meds.json
[
{
"name": "Simvastatin",
"dose": 10,
"dose unit": "mg",
"freq": "qd",
"route": "PO",
"last fill date": "2/15/2020",
"coverage": "100%",
"anticipated remaining fills": 2
},
{
"name": "Lisinopril",
"dose": 5,
"dose unit": "mg",
"freq": "qd",
"route": "PO",
"last fill date": "2/15/2020",
"coverage": "100%",
"anticipated remaining fills": 2
}
...... The list goes on
]
You should actually use a line chart for this which would be much more appropriate and exactly show the trend & relationship between month and the dosage.
But since you asked for scatter chart...you can do it like this :
const data = [{
"name": "Simvastatin",
"dose": 10,
"dose unit": "mg",
"freq": "qd",
"route": "PO",
"last fill date": "2/15/2020",
"coverage": "100%",
"anticipated remaining fills": 2
},
{
"name": "Lisinopril",
"dose": 5,
"dose unit": "mg",
"freq": "qd",
"route": "PO",
"last fill date": "2/15/2020",
"coverage": "100%",
"anticipated remaining fills": 2
}
]
const transformedData = data.map(obj=>{
return {
x:new Date(obj["last fill date"]).getMonth() + 1,
y:obj.dose,
}
})
console.log(transformedData)
and then use as
var scatterChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: transformedData
}]
},
options: {
scales: {
xAxes: [{
type: 'linear',
position: 'top'
}]
}
}
});
}
Hope this helps !
I want to create a synchronized High chart with two charts and the 1st chart will have one series and 2nd chart will have 2 series.
this link contains the sync chart with two series each chart, but when I reduced data to my required condition crosshair did not work properly.
data for chart.
var activity = {
"xData": [1, 1056, 2161, 3215, 4267],
"datasets": [{
"name": "Chart 1 series 1",
"data": [0, 10, 20, 30, 20],
"unit": "ms",
"type": "line",
"valueDecimals": 1
}, {
"name": "Chart 1 series 2",
"data": [23, 84, 22, 5, 75],
"unit": "ms",
"type": "line",
"valueDecimals": 1
}, {
"name": "Chart 2 series 1",
"data": [0, 10, 20, 30, 20],
"unit": "%",
"type": "line",
"valueDecimals": 1
}]
You are going from 2 series on each chart to 2 series on the 1st chart and 1 series on the 2nd chart, so you will be getting an error while trying your updated activity data. You need to add a conditional check like this (comments in uppercase):
/*
The purpose of this demo is to demonstrate how multiple charts on the same page can be linked
through DOM and Highcharts events and API methods. It takes a standard Highcharts config with a
small variation for each data set, and a mouse/touch event handler to bind the charts together.
*/
$(function () {
/**
* In order to synchronize tooltips and crosshairs, override the
* built-in events with handlers defined on the parent element.
*/
$('#container').bind('mousemove touchmove', function (e) {
var chart,
points,
i;
for (i = 0; i < Highcharts.charts.length; i++) {
chart = Highcharts.charts[i];
e = chart.pointer.normalize(e); // Find coordinates within the chart
// CHECK IF WE HAVE 2:
if ( chart.series.length === 2 ){
points = [chart.series[0].searchPoint(e, true), chart.series[1].searchPoint(e, true)]; // Get the hovered point
if (points[0] && points[1]) {
points[0].onMouseOver(); // Show the hover marker
points[1].onMouseOver(); // Show the hover marker
chart.tooltip.refresh(points); // Show the tooltip
chart.xAxis[0].drawCrosshair(e, points[0]); // Show the crosshair
}
// CHECK IF WE HAVE 1 CHART:
} else {
points = [chart.series[0].searchPoint(e, true)]; // Get the hovered poi
if (points[0]) {
points[0].onMouseOver(); // Show the hover marker
chart.tooltip.refresh(points); // Show the tooltip
chart.xAxis[0].drawCrosshair(e, points[0]); // Show the crosshair
}
}
}
});
/**
* Override the reset function, we don't need to hide the tooltips and crosshairs.
*/
Highcharts.Pointer.prototype.reset = function () {};
/**
* Synchronize zooming through the setExtremes event handler.
*/
function syncExtremes(e) {
var thisChart = this.chart;
Highcharts.each(Highcharts.charts, function (chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max);
}
}
});
}
// Get the data. The contents of the data file can be viewed at
// https://github.com/highslide-software/highcharts.com/blob/master/samples/data/activity.json
//$.getJSON('http://www.highcharts.com/samples/data/jsonp.php?filename=activity.json&callback=?', function (activity) {
var activity = {
"xData": [1, 1056, 2161, 3215, 4267],
"datasets": [{
"name": "Chart 1 series 1",
"data": [0, 10, 20, 30, 20],
"unit": "ms",
"type": "line",
"valueDecimals": 1
}, {
"name": "Chart 1 series 2",
"data": [23, 84, 22, 5, 75],
"unit": "ms",
"type": "line",
"valueDecimals": 1
}, {
"name": "Chart 2 series 1",
"data": [0, 10, 20, 30, 20],
"unit": "%",
"type": "line",
"valueDecimals": 1
}]
},
lastChart;
$.each(activity.datasets, function (i, dataset) {
// Add X values
dataset.data = Highcharts.map(dataset.data, function (val, i) {
return [activity.xData[i], val];
});
if(i%2 == 0) { //first series of chart
$('<div class="chart">')
.appendTo('#container')
.highcharts({
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20,
// zoomType: 'x'
// pinchType: null // Disable zoom on touch devices
},
title: {
text: dataset.name.slice(0,7),
align: 'left',
margin: 0,
x: 30
},
credits: {
enabled: false
},
legend: {
enabled: false
},
xAxis: {
crosshair: true,
events: {
setExtremes: syncExtremes
},
labels: {
format: '{value} km'
}
},
yAxis: {
title: {
text: null
}
},
tooltip: {
shared: true,
headerFormat: '',
valueDecimals: dataset.valueDecimals
},
series: [{
data: dataset.data,
name: dataset.name,
type: dataset.type,
color: Highcharts.getOptions().colors[i],
fillOpacity: 0.3,
tooltip: {
visible:true
}
}]
});
} else { //second series of chart
lastChart = Highcharts.charts[Highcharts.charts.length-1];
lastChart.addSeries({
data: dataset.data,
name: dataset.name,
type: dataset.type,
color: Highcharts.getOptions().colors[i],
fillOpacity: 0.3,
tooltip: {
valueSuffix: ' ' + dataset.unit
}
});
}
});
//});
});
Working JSFiddle: http://jsfiddle.net/kostasx/kfwtv1zj/
I am using Highcharts to create a timeline chart which shows the flow of different "states" over time. The current implementation is at http://jsfiddle.net/hq1kdpmo/8/ and it looks like .
The current code is as follows:
Highcharts.chart('container', {
"chart": {
"type": "xrange"
},
"title": {
"text": "State Periods"
},
"xAxis": {
"type": "datetime"
},
"yAxis": [{
"title": {
"text": "Factions"
},
"categories": ["A", "B", "C", "D"],
"reversed": true
}],
"plotOptions": {
"xrange": {
"borderRadius": 0,
"borderWidth": 0,
"grouping": false,
"dataLabels": {
"align": "center",
"enabled": true,
"format": "{point.name}"
},
"colorByPoint": false
}
},
"tooltip": {
"headerFormat": "<span style=\"font-size: 0.85em\">{point.x} - {point.x2}</span><br/>",
"pointFormat": "<span style=\"color:{series.color}\">●</span> {series.name}: <b>{point.yCategory}</b><br/>"
},
"series": [{
"name": "State A",
"pointWidth": 20,
"data": [{
"x": 1540430613000,
"x2": 1540633768100,
"y": 0
}, {
"x": 1540191009000,
"x2": 1540633768100,
"y": 1
}, {
"x": 1540191009000,
"x2": 1540530613000,
"y": 2
}, {
"x": 1540530613000,
"x2": 1540633768100,
"y": 3
}]
}, {
"name": "State B",
"pointWidth": 20,
"data": [{
"x": 1540191009000,
"x2": 1540430613000,
"y": 0
}, {
"x": 1540530613000,
"x2": 1540633768100,
"y": 2
}, {
"x": 1540191009000,
"x2": 1540330613000,
"y": 3
}]
}, {
"name": "State C",
"pointWidth": 20,
"data": [{
"x": 1540330613000,
"x2": 1540530613000,
"y": 3
}]
}],
"exporting": {
"enabled": true,
"sourceWidth": 1200
}
});
Now what I am looking forward to is create something of this sort (pardon my paint skills).
Here the categories A to D are the only axis on y. But I would like to group a variable number of parallel ranges. The use case is that there can be multiple states at any point of time and the number of states at any point of time is variable. How do I go on about doing this?
To create such a chart you will have to add 12 yAxis (0-11) and set proper ticks and labels so that only A-D categories will be plotted. Additionally, adjust plotOptions.pointPadding and plotOptions.groupPadding properties to set points width automatically (series.pointWidth should be undefined then).
yAxis options:
yAxis: [{
title: {
text: "Factions"
},
categories: ["A", "B", "C", "D"],
tickPositions: [-1, 2, 5, 8, 11],
lineWidth: 0,
labels: {
y: -20,
formatter: function() {
var chart = this.chart,
axis = this.axis,
label;
if (!chart.yaxisLabelIndex) {
chart.yaxisLabelIndex = 0;
}
if (this.value !== -1) {
label = axis.categories[chart.yaxisLabelIndex];
chart.yaxisLabelIndex++;
if (chart.yaxisLabelIndex === 4) {
chart.yaxisLabelIndex = 0;
}
return label;
}
},
},
reversed: true
}]
Demo:
https://jsfiddle.net/wchmiel/s9qefg7t/1/
I have managed to solve this thanks to support from Highcharts themselves. The idea is to set the tick position on the load event and use the labels.formatter for formatting each individual label.
events: {
load() {
let labelGroup = document.querySelectorAll('.highcharts-yaxis-labels');
// nodeValue is distance from top
let ticks = document.querySelectorAll('.highcharts-yaxis-grid');
let tickPositions = Array.from(ticks[0].childNodes).map(
function(node){
return +node.attributes.d.nodeValue.split(" ")[2];
}
);
let labelPositions = [];
for(let i =1 ;i<tickPositions.length;i++){
labelPositions.push((tickPositions[i] + tickPositions[i-1])/2);
}
labelGroup[0].childNodes[0].attributes.y.nodeValue = labelPositions[0] + parseFloat(labelGroup[0].childNodes[0].style["font-size"], 10) / 2;
labelGroup[0].childNodes[1].attributes.y.nodeValue = labelPositions[1] + parseFloat(labelGroup[0].childNodes[1].style["font-size"], 10) / 2;
labelGroup[0].childNodes[2].attributes.y.nodeValue = labelPositions[2] + parseFloat(labelGroup[0].childNodes[2].style["font-size"], 10) / 2;
labelGroup[0].childNodes[3].attributes.y.nodeValue = labelPositions[3] + parseFloat(labelGroup[0].childNodes[3].style["font-size"], 10) / 2;
labelGroup[0].childNodes[4].attributes.y.nodeValue = labelPositions[4] + parseFloat(labelGroup[0].childNodes[4].style["font-size"], 10) / 2;
}
}
And the labels are formatted as:
labels: {
formatter: function() {
var chart = this.chart,
axis = this.axis,
label;
if (!chart.yaxisLabelIndex) {
chart.yaxisLabelIndex = 0;
}
if (this.value !== -1) {
label = axis.categories[chart.yaxisLabelIndex];
chart.yaxisLabelIndex++;
if (chart.yaxisLabelIndex === groups.length) {
chart.yaxisLabelIndex = 0;
}
return label;
}
},
}
Fiddle at https://jsfiddle.net/yvnp4su0/42/
As I suggested in the comment above it is a better idea to use Highcharts renderer and add custom labels than manipulate Dom elements as you did in the previous answer, because it is a much cleaner solution.
Disable default labels:
yAxis: [{
title: {
text: "Factions",
margin: 35
},
categories: ["A", "B", "C", "D", "E"],
tickPositions: tickPositions,
lineWidth: 0,
labels: {
enabled: false
},
reversed: true
}]
Add custom labels in proper positions using renderer:
chart: {
type: 'xrange',
height: 500,
marginLeft: 60,
events: {
load: function() {
this.customLabels = [];
},
render: function() {
var chart = this,
yAxis = chart.yAxis[0],
categories = yAxis.categories,
xOffset = 15,
yOffset = 20,
xPos = yAxis.left - xOffset,
tickPositions = yAxis.tickPositions,
text,
label,
yPos,
tick1Y,
tick2Y,
i;
for (i = 0; i < tickPositions.length - 1; i++) {
if (chart.customLabels[i]) {
chart.customLabels[i].destroy();
}
tick1Y = yAxis.toPixels(tickPositions[i]);
tick2Y = yAxis.toPixels(tickPositions[i + 1]);
yPos = (tick1Y + tick2Y) / 2 + yOffset;
text = categories[i];
label = chart.renderer.text(text, xPos, yPos)
.css({
color: '#ccc',
fontSize: '14px'
})
.add();
chart.customLabels[i] = label;
}
}
}
}
Demo: https://jsfiddle.net/wchmiel/vkz7o1hw/
Basically, am working with json objects however my skills are not that much because I am still a newbie in javascript and json or object literals. Am trying to achieve where I would like to push or insert a custom json object at the last element of the other xml/json file. Is there any on way how to do this? I've been trying to do it for quite some time now but could not make it work. Any idea how to make it work? because honestly, I don't have any left :-)
I use getJSON to request a JSON from my website. It works great, but I need to somehow insert another custom object literals at the end of the json is it possible?
By the way here is my code.
$(function() {
$.getJSON('https://some_link_from_a_server_that_produces_xml_file_or_json',
function(data) {
//var dataLength = data.length;
fillData();
function fillData() {
var jsonData = [{
"LastModification": "04:27:48",
"Symbol": "EURUSD",
"Bid": '1.20568',
"Ask": "1.21238",
"High": '1.21789',
"Low": '1.19253',
"Direction": "-1",
"InserTime": "\/Date(1358760600163)\/",
"volume": "0"
}];
for (var i = 0; i < jsonData.length; i++) {
data.push([
parseFloat(jsonData[i].Bid),
parseFloat(jsonData[i].High),
parseFloat(jsonData[i].Low),
parseFloat(jsonData[i].Ask),
parseInt(jsonData[i].InserTime.substr(6)),
parseInt(jsonData[i].volume)
]);
}
CreateChart();
} // end of function fillData()
function CreateChart() {
var chart = new Highcharts.stockChart('container2',
{
title: {
text: 'EUR/USD',
floating: true,
align: 'left',
x: 0,
y: 55
},
subtitle: {
text: 'highest: 1.23223 / lowest: 1.21774',
floating: true,
align: 'left',
x: 0,
y: 70
},
xAxis: {
gridLineWidth: 1
},
yAxis: {
gridLineWidth: 1
},
rangeSelector: {
buttons: [
{
type: 'hour',
count: 1,
text: '1h'
}, {
type: 'day',
count: 1,
text: '1D'
}, {
type: 'all',
count: 1,
text: 'All'
}
],
selected: 1,
inputEnabled: true
},
series: [
{
name: 'EURUSD',
type: 'candlestick',
data: data,
tooltip: {
valueDecimals: 5
}
}
]
}); // end of highcharts.stockchart
}
});
});
I think you are making a array of JSON which looks like [{...}, {...}]
However, in your code you are making an array of array which looks like [[...], [...]].
Try :
let data = [];
const func = function(data) {
fillData();
function fillData() {
var jsonData = [{
"LastModification": "04:27:48",
"Symbol": "EURUSD",
"Bid": '1.20568',
"Ask": "1.21238",
"High": '1.21789',
"Low": '1.19253',
"Direction": "-1",
"InserTime": "\/Date(1358760600163)\/",
"volume": "0"
}];
for (var i = 0; i < jsonData.length; i++) {
data.push({
Bid :parseFloat(jsonData[i].Bid),
High : parseFloat(jsonData[i].High),
Low : parseFloat(jsonData[i].Low),
Ask : parseFloat(jsonData[i].Ask),
InserTime : parseInt(jsonData[i].InserTime.substr(6)),
Volume :parseInt(jsonData[i].volume)
});
}
console.log(data);
} // end of function fillData()
}
func(data);
I'm trying to dynamically replace series data and plot bands in my Highcharts gauge. So far I've managed to dynamically change the series data, however I am stuck struggling to replace the bands. This is what I've tried:
let highchartsChartOptions = {
"chart": {
"type": "gauge",
"renderTo": "chart"
},
"series": [{
"data": [247600]
}],
"yAxis": {
"plotBands": [{
"from": 156700,
"to": 277150,
"color": "#ff0000",
}, {
"from": 277150,
"to": 386100,
"color": "#00ff00"
}],
"min": 100000,
"max": 400000
},
"pane": {
"background": null,
"startAngle": -90,
"endAngle": 90
}
};
let seriesData = [
[226800],
[247600]
];
let seriesBands = [
[{
"from": 156700,
"to": 277150,
"color": "#ff0000",
}, {
"from": 277150,
"to": 386100,
"color": "#00ff00"
}],
[{
from: 100000,
to: 250000,
"color": "#ff0000"
}, {
from: 250000,
to: 400000,
"thickness": 15,
}]
];
const replacePlotBand = (axis, id, replacement) => {
axis.removePlotBand(id);
axis.addPlotBand(replacement);
};
let chart = new Highcharts.Chart(highchartsChartOptions);
let flip = true;
$("#change").on("click", () => {
chart.series[0].setData(seriesData[flip ? 0 : 1]);
chart.xAxis[0].update({
plotBands: seriesBands[flip ? 0 : 1]
});
chart.redraw(true);
flip = !flip;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://code.highcharts.com/highcharts.src.js"></script>
<script src="https://code.highcharts.com/highcharts-more.src.js"></script>
<div id='chart' style="width: 800px; height: 500px;"></div>
<button id="change">Change data</button>
My intention is to replace both series data and plot bands with a different set of data but right now only the series data seems to be updated.
I'm not sure what I am doing wrong here.
I have also tried adding IDs to the bands and doing:
chart.xAxis[0].removePlotBand('band1');
chart.xAxis[0].removePlotBand('band2');
chart.xAxis[0].addPlotBand(seriesBands[flip ? 0 : 1][0]);
chart.xAxis[0].addPlotBand(seriesBands[flip ? 0 : 1][1]);
but that also did not work (see fiddle https://jsfiddle.net/yc2gnp7x/2/).
Your highChartOptions uses yAxis but you are removing and adding to xAxis