Apexcharts - bars are too small - timeline

I am using a timeline chart from Apexcharts 3.19.0 and I noticed that every time I add a new vertical "category" the bars start to shrink. Is it possible to set the bar height a fixed size?
I am building a timeline chart that represents a production line. Bars are the production and the categories are the machines. And one build is related only to one machine.
This is the series that I pass and if I continue to add new machines bars continue to shrink.
I noticed that Apexcharts makes every bar with such height that every row can take all bars, but I don't need this in my case.
[
{
"name": "B-2004281001-6763",
"data": [
{
"x": "Cube 3-1",
"y": [
1588068083109,
1588071676403
],
}
]
},
{
"name": "B-2004281000-8133",
"data": [
{
"x": "BiZon Prusa i3 Steel-2",
"y": [
1588068021615,
1588075213496
],
}
]
},
{
"name": "B-2004281001-9110",
"data": [
{
"x": "BiZon Prusa i3 Steel-2",
"y": [
1588068068356,
1588078856311
],
}
]
}
]
That's how my chart looks like
My Chart

I had a similar issue and I got around it by using a common shared "x" value ("Production" in your example) and setting "name" values in the series data arrays that were then displayed by dataLabels.
So your modified data:
[
{
name: "B-2004281001-6763",
data: [{ name: "Cube 3-1", x: "Production", y: [ 1588068083109, 1588071676403 ] }]
},
{
name: "B-2004281000-8133",
data: [{ name: "BiZon Prusa i3 Steel-2", x: "Production", y: [1588068021615, 1588075213496 ] }]
},
{
name: "B-2004281001-9110",
data: [{ name: "BiZon Prusa i3 Steel-2", x: "Production", y: [1588068068356, 1588078856311 ], } ]
}
]
and the corresponding dataLabels option (black text color for visibility)
dataLabels: {
enabled: true,
formatter: function(val, opts) {
return opts.w.config.series[opts.seriesIndex].data[opts.dataPointIndex].name;
},
style: {
colors: ["#000000"]
}
}
Check it out here

Related

How to use attr fill with scalesqrt using data from another json file?

I'm new to d3 and I'm trying to color each US state based on the total energy consumption value. When I use :
d3.selectAll("path")
.attr("fill", "blue");
I can successfully change the color to blue, so I think I have the right foundation. But when I try to apply the colorgradient function that I have defined myself, it doesn't work anymore. Most of the examples I have seen online use the same json file to create the map and to assign color values. But I have two json files I'm working with--one that contains the US state path shape and the other with meta data for each state. This is what I have:
const data = await d3.json("states.json");
const energy_data = await d3.csv("Energy Census and Economic Data US 2010-2014.csv");
console.log(energy_data);
const states = topojson.feature(data, data.objects.usStates).features
// draw the states
svg.selectAll(".state")
.data(states)
.enter().append("path")
.attr("class", "state")
.attr("d", path);
const min = d3.min(energy_data, d => parseInt(d.TotalC2010));
const max = d3.max(energy_data, d => parseInt(d.TotalC2010));
const colorgradient = d3.scaleSqrt()
.domain([min, max])
.range(["green", "blue"]);
d3.selectAll("path")
.data(energy_data)
.enter()
.attr("fill", d => colorgradient(d.TotalC2010));
Any advice?
Thanks in advance!
EDIT: I finally got it to work, thanks to Andrew Reid.
const state_data = await d3.json("states.json");
const energy_data_raw = await d3.csv("Energy Census and Economic Data US 2010-2014.csv");
const energy_data = new Map(energy_data_raw.map(d => [d.StateCodes, d]))
const states = topojson.feature(state_data, state_data.objects.usStates).features
const min = d3.min(energy_data_raw, d => parseInt(d.TotalC2010));
const max = d3.max(energy_data_raw, d => parseInt(d.TotalC2010));
let colorgradient = d3.scaleSqrt()
.domain([min, max])
.range(["green", "blue"]);
svg.selectAll(".state")
.data(states)
.enter()
.append("path")
.attr("class", "state")
.attr("d", path)
.attr("fill", d => colorgradient(energy_data.get(d.properties.STATE_ABBR).TotalC2010))
Problem
This code here:
d3.selectAll("path")
.data(energy_data)
.enter()
.attr("fill", d => colorgradient(d.TotalC2010));
Shouldn't do anything at all.
First, assuming your geographic data and your energy data have the same number of items, you are selecting the existing paths (d3.selectAll("path")).
Then you are assigning those existing paths new data (.data(energy_data)) matching each existing path - in order of their index.
Next you create what is likely an empty enter selection. The enter selection creates an element for every item in the data array that does not have a corresponding element (matched by index here): if you have more items in your data array than elements, you'll enter new elements. Otherwise, you'll have an empty selection because you do no need to enter any new elements to represent the data in energy_data.
Lastly you style an empty enter selection (normally you'd use .append() to specify what type of element you'd want to append, otherwise, the enter selection is just a placeholder. As this is an empty selection anyways, nothing is done.
Possible Solution
I'm going through this solution as it looks like what you are trying to do, though it is not what I would recommend.
It appears as though you are trying to assign new data to an existing selection - something that is absolutely possible. In this approach you'd use the geographic data to draw the features, then assign a new dataset to each feature and modify the features based on this new data.
Data arrays ordered the same
If our data is in the same order in both geojson and csv, then we can simply use:
selection.data(states)
.enter()
.append("path")
.attr("d", path)
.data(energy_data)
.attr("fill", ...)
Because .data() by default binds items in the data array to elements in the selection by matching their indices, each feature is updated with the correct data in energy_data only when the two data arrays are ordered the same. This is an obvious limitation, but one that can be overcome.
Data arrays ordered differently
If the arrays are not ordered the same, we need to have a way to match existing features with the new data set. By default the .data() method assigns data to existing elements by index. But we can use the second parameter of .data() to assign a unique identifier using a key function.
For this case I'm assuming our identifier for both states and energy_data resides at d.properties.id.
When we enter our paths, we don't need the key function, there is no data to join to existing elements.
When we update our paths with the energy_data data, we want to use a key function to ensure we update each element with the correct new data. The key function is evaluated on each existing element's datum first, and then on each item in the new data array. Where a match in key is found, the matching new datum will replace the old.eg:
svg.selectAll("path")
.data(energy_data, function(d) { return d.properties.id; })
.attr("fill",...
Here's a quick example with contrived data:
let data = [
{ value: 4, properties: {id: "A" }},
{ value: 6, properties: {id: "B" }},
{ value: 2, properties: {id: "C" }}
]
let color = d3.scaleLinear()
.domain([1,6]).range(["red","yellow"]);
let geojson = {
"type":"FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { id: "C"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 0, 0 ],
[ 100, 0 ],
[ 100,100 ],
[ 0, 100 ],
[ 0, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "B"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 100, 0 ],
[ 200, 0 ],
[ 200, 100 ],
[ 100, 100 ],
[ 100, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "A"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 200, 0 ],
[ 300, 0 ],
[ 300,100 ],
[ 200, 100 ],
[ 200, 0 ]
]
]
}
}
]
}
let svg = d3.select("body")
.append("svg")
.attr("width", 300);
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", d3.geoPath(null));
svg.selectAll("path") // Note: You can just chain .data() to .attr() omitting this line.
.data(data, d=>d.properties.id)
.attr("fill", d=>color(d.value));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.0/d3.min.js"></script>
Data arrays ordered differently with different key accessors
However, if the location or name of the identifier is different than before, we need to change the key function.
For this case, I'm assuming that the states identifier is located at d.properties.id. For energy_data, I'm assuming that the identifier resides at d.id, a more common parsed DSV location.
As noted, the key function is evaluated for existing element's data and then new data. this means we need a key function that works for both datasets, which means we need a slightly more complicated key function to compare items from both datasets, for example:
.data(energy_data, function(d) {
if(d.properties)
return d.properties.id; // get the key from items in `states`
else
return d.id; // get the key from items in `energy_data`
})
.attr("fill",...
The key function now will be able to have the new datum replace the old ensuring that the correct feature has the correct data.
Assuming all your identifiers match properly (and are strings) you'll have assigned new data to the existing features.
The downside of this approach is you've lost the original data - if you want to do semantic zooming, check different properties of the geographic data, or revisit the data in the geojson, you need to rebind the original data. Selecting the paths takes time as well, and it assumes there are no other paths that might be mistakenly selected.
Here's a quick example:
let csv = d3.csvParse(d3.select("pre").remove().text());
let color = d3.scaleLinear()
.domain([1,6]).range(["red","yellow"]);
let geojson = {
"type":"FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { id: "C"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 0, 0 ],
[ 100, 0 ],
[ 100,100 ],
[ 0, 100 ],
[ 0, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "B"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 100, 0 ],
[ 200, 0 ],
[ 200, 100 ],
[ 100, 100 ],
[ 100, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "A"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 200, 0 ],
[ 300, 0 ],
[ 300,100 ],
[ 200, 100 ],
[ 200, 0 ]
]
]
}
}
]
}
let svg = d3.select("body")
.append("svg")
.attr("width", 300);
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", d3.geoPath(null));
svg.selectAll("path") // Note: You can just chain .data() to the .attr() omitting this line.
.data(csv, d=>d.properties?d.properties.id:d.id)
.attr("fill", d=>color(d.value));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.0/d3.min.js"></script>
<pre>id,value
A,4
B,6
C,2</pre>
Recommended Approach
To join the spatial data and non spatial data, I suggest using a javascript map. This allows you do look up values in your non spatial data using the shared identifier:
let map = new Map(energy_data.map(function(d) { return [d.id, d] }))
We can look up any item in energy_data now with map.get("someIdentifier")
Which we can use as follows:
.attr("fill", d=> colorgradient(map.get(d.properties.id).TotalC2010))
This way our spatial features retain their spatial data, but we can easily access the nonspatial data using the common identifier and the javascript map.
Here's a quick example using the same contrived geojson and DSV data as above:
let csv = d3.csvParse(d3.select("pre").remove().text());
let map = new Map(csv.map(function(d) { return [d.id, d] }))
let color = d3.scaleLinear()
.domain([1,6]).range(["red","yellow"]);
let geojson = {
"type":"FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { id: "C"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 0, 0 ],
[ 100, 0 ],
[ 100,100 ],
[ 0, 100 ],
[ 0, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "B"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 100, 0 ],
[ 200, 0 ],
[ 200, 100 ],
[ 100, 100 ],
[ 100, 0 ]
]
]
}
},
{
"type": "Feature",
"properties": { id: "A"},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 200, 0 ],
[ 300, 0 ],
[ 300,100 ],
[ 200, 100 ],
[ 200, 0 ]
]
]
}
}
]
}
let svg = d3.select("body")
.append("svg")
.attr("width", 300);
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", d3.geoPath(null))
.attr("fill", d=> color(map.get(d.properties.id).value));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.0/d3.min.js"></script>
<pre>id,value
A,4
B,6
C,2</pre>
Other Approaches
A third option would be to combine the data arrays - iterating through the geojson and adding values contained in energy_data to each feature manually so that you have only one data array containing everything you need to draw and style the visualization.

ChartJS: How to center monthly bars against a daily line chart?

I'm trying to display total monthly sales and daily stock level. This way you could easily see that you didn't have any sales a particular month because you had low stock. Monthly sales is a bar chart that should be in the center of each month (in between the ticks).
In order to get it close to the middle my data is using the 15th of each month as the date to center it. I would want to know if there is a better way to achieve this?
JSFiddle to play around with: https://jsfiddle.net/8Lydhpqc/3/
const dailyStock = [
{ x: "2017-08-02", y: 1 },
{ x: "2017-08-25", y: 3 },
{ x: "2017-09-10", y: 7 },
{ x: "2017-09-28", y: 0 },
{ x: "2017-10-02", y: 3 },
{ x: "2017-10-24", y: 2 },
{ x: "2017-11-01", y: 1 },
{ x: "2017-11-30", y: 0 },
];
//using the 15th of each month to center it
const monthlyTotal = [
{ x: "2017-08-15", y: 1 },
{ x: "2017-09-15", y: 10 },
{ x: "2017-10-15", y: 5 },
{ x: "2017-11-15", y: 5 },
];
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["2017-08", "2017-09", "2017-10", "2017-11"],
datasets: [
{
label: "sales",
data: data,
backgroundColor: "green",
borderColor: "black",
borderWidth: 1,
order: 2,
},
{
label: "stock",
type: "line",
data: dailyStock,
backgroundColor: "orange",
borderColor: "orange",
fill: false,
order: 1,
},
],
},
options: {
scales: {
xAxes: [
{
type: "time",
time: {
unit: "month",
displayFormats: {
month: "MMM",
},
},
distribution: "linear",
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});
Welcome to Stackoverflow!
It seems that there is a way better than using the 15th of the month.
You need to add another axis for the bar that is a category type axis. Also its pretty critical that you have "offset: true" on that axis as well. Otherwise it will not center.
In the code below I named that category "bar" and the existing one "line"
I also created a fiddle: https://jsfiddle.net/jyf8ax3e/
var myChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["2017-08", "2017-09", "2017-10", "2017-11"],
datasets: [
{
barPercentage: .7,
xAxisID: "bar",
label: "sales",
data: monthlyTotal,
backgroundColor: "green",
borderColor: "black",
borderWidth: 1,
width: 55,
order: 2,
},
{
label: "stock",
type: "line",
data: dailyStock,
backgroundColor: "orange",
borderColor: "orange",
fill: false,
order: 1,
},
],
},
options: {
scales: {
xAxes: [
{
id: "line",
type: "time",
time: {
unit: "month",
displayFormats: {
month: "MMM",
},
},
distribution: "linear",
},
{
id: "bar",
offset: true,
type: "category",
distribution: "series",
}
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});

AmCharts Adding Background Color to valueAxes guides label

How can I add background color to valueAxes guides label?
Seems like there is no options that we can set or I just really dont know what it is.
This is current setup that I have in amcharts
$chart = AmCharts.makeChart( "chartdiv", {
"type": "serial",
"theme": "light",
"dataProvider": $data_trade,
"valueAxes": [ {
"position": "right",
"guides": [ {
"value": $tickValue,
"label": $tickValue,
"position": "right",
"dashLength": 0,
"axisThickness": 1,
"fillColor": "#000",
"axisAlpha": 1,
"fillAlpha": 1,
"color": "#000",
"fontSize": 16,
"backgroundColor": "#008D00",
"labelColorField": "red",
},
],
} ],
../
} );
please see image for reference
image-screenshot
Im new here, I hope I can get help
Thanks
There isn't a built-in way to do this currently but you can use the same technique in this demo to create a colored box around your label in the drawn event by changing the selector to .amcharts-axis-label.amcharts-guide to target the guide label and apply your color there. Note that the demo doesn't set individual colors, but the drawn event gives you access to the chart object if you want to pull the color from your custom backgroundColor properties:
AmCharts.makeChart("...", {
// ...
"valueAxes": [{
// ...
"guides": [{
"value": 4.5,
"label": "4.5",
"backgroundColor": "#22ff11" //custom property for drawn event
}, {
"value": 7.5,
"label": "7.5",
"backgroundColor": "#11ddff"
}]
}],
// ...
"listeners": [{
"event": "drawn",
"method": addLabelBoxes
}]
});
function addLabelBoxes(event) {
var labels = document.querySelectorAll(".amcharts-axis-label.amcharts-guide");
Array.prototype.forEach.call(labels, function(label, i) {
var parent = label.parentNode;
var labelText = label.childNodes[0].textContent; //get guide label from SVG
var svgRect = label.getBBox();
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
// find the matching guide label in the chart object
var guide = event.chart.valueAxes[0].guides.filter(function(guide) {
return guide.label == labelText;
});
rect.setAttribute("x", svgRect.x);
rect.setAttribute("y", svgRect.y);
rect.setAttribute("transform", label.getAttribute("transform"))
rect.setAttribute("width", svgRect.width);
rect.setAttribute("height", svgRect.height);
rect.setAttribute("fill", (guide && guide.length && guide[0].backgroundColor ? guide[0].backgroundColor : "#FFD32F")); //apply background from guide if it exists
rect.setAttribute("stroke", (guide && guide.length && guide[0].backgroundColor ? guide[0].backgroundColor : "#4857FF")); //same for the border
rect.setAttribute("opacity", 1);
parent.insertBefore(rect, label);
});
}
Demo

c3.js with json ,split line line

I have a JSON array and want to make a chart with C3.js. My example gives me only one line. I want to display however one line for every user. How can I achieve this?
Here is my code:
this.chart = c3.generate({
data: {
json: [
{
ExamID: 'Exam1',
result: 80,
user:"user1"
},
{
ExamID: 'Exam2',
result: 90,
user:"user1"
},
{
ExamID: 'Exam1',
result: 70,
user:"user2"
},
{
ExamID: 'Exam2',
result: 60,
user:"user2"
}
],
keys: {
x: 'ExamID',
value: ['result'],
},
},
axis: {
x: {
type: 'category',
}
}
});
You need to have a different JSON structure. Every object in the array (every exam) needs to have the results of all students in it. You can use a key for every student and rename the key for the legend with the attribute names. Here is a Fiddle: https://jsfiddle.net/b5pd3wn6/
var chart = c3.generate({
bindto: "#element",
data: {
json: [
{
ExamID: 'Exam1',
user1: 80,
user2: 50
}, {
ExamID: 'Exam2',
user1: 90,
user2: 40
}
],
keys: {
x: 'ExamID',
value: ['user1', 'user2']
},
names: {
'user1': 'Peter Miller',
'user2': 'Vladimir Peterhans'
}
},
axis: {
x: {
type: 'category'
}
}
});

Kendo UI grid exporting date column data as ###############

****example: http://jsfiddle.net/o8cw3vj8/12/
I am trying to export kendo ui grid and grid has date column, when exported it contains only ############... when date year is less than or equal to 1900
Thanks,****
$("#grid1").kendoGrid({
toolbar: ["excel"],
excel: {
fileName: "Kendo UI Grid Export.xlsx",
proxyURL: "http://demos.telerik.com/kendo-ui/service/export",
filterable: true
},
dataSource: {
data: [{
"Id": 1,
"Name": "John",
"DOB": "01/01/1753"
}, {
"Id": 2,
"Name": "Joe",
"DOB": "01/01/1953"
}, {
"Id": 3,
"Name": "Jack",
"DOB": "01/01/1940"
}],
schema: {
model: {
fields: {
Id: {
type: "number"
},
Name: {
type: "string"
},
DOB: {
type: "date"
}
}
}
}
},
columns: [
"Id", "Name", "DOB"
]
});
If you hover the cell you will see a message "Dates and times that are negative or too large display as #####". Here is a screenshot:
This happens because Excel saves dates as numbers of days after 1/1/1990. In your case the numbers become negative hence the display.

Resources