I need to trigger hover of a specific column and/or segment in an XYChart (amCharts). I've managed to make it work, however it feels like there should be an easier way.
The scenario is that the chart data is also displayed in a table next to the chart. When a row in the table is hovered, I want to set hover of the corresponding column.
The data looks something like this:
const data = [{
x: 'Kalle',
value: 12,
meta: {id: 12432341}
},{
x: 'Nisse',
value: 20,
meta: {id: 54251114}
},{
x: 'Bosse',
value: 8,
meta: {id: 16517346}
}]
And the chart is built like this:
const chart = am4core.create('chartArea', am4charts.XYChart)
// Add category axis
const xAxis = chart.xAxes.push(new am4charts.CategoryAxis())
xAxis.data = categories.map((c) => ({x: c}))
xAxis.dataFields.category = 'x'
// Add value axis
const axis = chart.yAxes.push(new am4charts.ValueAxis())
// Add column series
const series = chart.series.push(new am4charts.ColumnSeries())
series.data = data
series.dataFields.valueY = 'value'
series.dataFields.categoryX = 'x'
series.yAxis = axis
series.bullets.push(new am4charts.CircleBullet())
series.columns.template.interactionsEnabled = true
const hs = series.columns.template.states.create('hover')
hs.properties.strokeWidth = 8
const bullet = series.bullets.push(new am4charts.CircleBullet())
And then the function that should find a specific column and set hover:
setHover(chart, id) {
let xPosition
chart.series.each(series => {
if (!xPosition) {
const data = series.data.find(d => d.meta && d.meta.id === id)
if (!data) {
return
}
xPosition = chart.xAxes.getIndex(0).dataItems.values.findIndex(v => v.category === data.x)
}
const column = series.columns.getIndex(xPosition)
if (column) {
column.isHover = true
}
})
}
While this works, I'd prefer to identify the column directly instead of going by the category axis position. This would make more sense to me:
setHover(chart, id) {
chart.series.each(series => {
const column = series.columns.values.find(column => column.dataItem.meta.id === id)
if (column) {
column.isHover = value
}
})
}
Or, could it be done with adapter instead?
What's the most straight forward way?
Related
I want to show data by current month and show 'no data to display' message if no data exists on current month. I managed to show all data ascending by date, but this time I only want it to display the data by current month. Oh, I'm using Laravel & ChartJS.
Here is the data query on controller :
$achieveDaily = DailyProduction::orderBy('tgl', 'asc')->get();
Here is the foreach function to display data :
foreach($achieveDaily as $ad){
$labels1[] = Carbon::parse($ad->tgl)->format('j');
$plan1[] = $ad->plan;
$actual1[] = $ad->actual;
};
And here is the chartjs script :
<script>
var labelsL1 = JSON.parse('{!! json_encode($labels1) !!}');
var plan = JSON.parse('{!! json_encode($plan1) !!}');
var actual = JSON.parse('{!! json_encode($actual1) !!}');
const data = {
labels: labelsL1,
datasets: [{
label: 'Plan',
data: plan,
backgroundColor: 'rgba(0, 0, 255, 0.8)',
datalabels:{
color: '#000000',
anchor: 'end',
align: 'top'
},
},
{
label: 'Actual',
data: actual,
backgroundColor: 'rgba(0, 255, 0, 1)',
datalabels:{
color: '#000000',
anchor: 'end',
align: 'top'
},
}]
};
const config = {
type: 'bar',
data,
plugins: [ChartDataLabels],
options: {
scales: {
y: {
beginAtZero: true
}
}
}
}
const ctx = document.getElementById('dailyAchieveChart');
const dailyAchieve1 = new Chart(
ctx,
config
);
You can use function whereMonth in laravel
DailyProduction::whereMonth('created_at', Carbon::now()->month)
->get();
So if you want to display data by current Month, can use whereDate() function. Why NOT combine whereMonth() and whereYear() together?
They will run separate query for just filtering specific month, and specific year and combine it.
So better query = better performance. Here's example :
$dailyProduction = DailyProduction::whereDate('tgl', 'like', Carbon::now()->format('Y-m') . '%')->orderBy('tgl')->get();
$labels = [1, 2, ...]; // Basically anything to your graph labels, i assume you want to show daily
$plan = $actual = [];
foreach($dailyProduction as $daily) {
$plan[] = $daily->plan;
$actual[] = $daily->actual;
}
At your graph, when no data is there, just keep it blank. Or use blade #if #else
#if(empty($dailyProduction))
<p>No Data</p>
#else
// Your Graph
#endif
At your <script>. I assume rest of your const data is right
var labelsL1 = {!! json_encode($labels) !!};
var plan = {!! json_encode($plan) !!};
var actual = {!! json_encode($actual) !!};
I think i already solved this by using if else condition, and then using isEmpty() to check if the collection is empty or not, if it's not empty, then run the foreach. Like this :
if($achieveDaily->isEmpty()){
$labels1[] = [];
$plan1[] = [];
$actual1[] = [];
}else{
foreach($achieveDaily as $ad){
$labels1[] = Carbon::parse($ad->tgl)->format('j');
$plan1[] = $ad->plan;
$actual1[] = $ad->actual;
}
};
Thank you for everyone who tried to help me! Appreciated it!
I have tried using the code below to display the top 3 values based on the count of the val in the json provided below.
yearRingChart.width(300).height(300).dimension(yearDim).group(
spendPertime).innerRadius(50).controlsUseVisibility(true)
.data(function(d) {
return d.order(
function(d) {
return d.val;
}).top(3);
}).ordering(function(d) {
return -d.val;
});
But this is getting sorted based on alphabetical order and not based on val.
I need to display the top 3 names with highest val in the json provided.
The code snippet is provided below -
var yearRingChart = dc.pieChart("#chart-ring-year"),
spendHistChart = dc.barChart("#chart-hist-spend"),
spenderRowChart = dc.rowChart("#chart-row-spenders");
var table = dc.dataTable('#table');
// use static or load via d3.csv("spendData.csv", function(error, spendData) {/* do stuff */});
var spendData = [{
Name : 'A',
val : '100',
time : 9,
place : 'Kolkata'
}, {
Name : 'B',
val : '40',
time : 10,
place : 'Angalore'
}, {
Name : 'C',
val : '5',
time : 11,
place : 'Raipur'
}, {
Name : 'A',
val : '70',
time : 12,
place : 'Chennai'
}, {
Name : 'B',
val : '20',
time : 10,
place : 'Mumbai'
}];
// normalize/parse data
spendData.forEach(function(d) {
d.val = d.val.match(/\d+/)[0];
});
// set crossfilter
var ndx = crossfilter(spendData), yearDim =
ndx.dimension(function(
d) {
return d.place;
}), spendDim = ndx.dimension(function(d) {
return Math.floor(d.val / 10);
}), nameDim = ndx.dimension(function(d) {
return d.Name;
}), spendPertime = yearDim.group().reduceSum(function(d) {
return +d.val;
})
, spendPerSev = yearDim.group().reduceSum(function(d) {
return +d.val;
}), spendPerName = nameDim.group().reduceSum(function(d) {
return +d.val;
}), spendHist = spendDim.group().reduceCount();
yearRingChart.width(300).height(300).dimension(yearDim).group(
spendPertime).innerRadius(50).controlsUseVisibility(true)
.data(function(d) {
return d.order(
function(d) {
return d.val;
}).top(3);
}).ordering(function(d) {
return -d.ue;
});
I am a newbie in dc.js. Any help will be appreciated.
Thankyou
I suspect that the big problem you are running into is that .ordering() - which is the right way to do this - takes group data, not raw data. Once you have aggregated your data using a group, you are going to have an array of {key,value} pairs, not your original array of data.
So I think when you attempt to read d.val or d.ue, you're just getting undefined, which results in no sorting. You could verify this by putting a breakpoint or console.log in your ordering callbacks. That's how I usually debug these kinds of things, when I have a running example.
group.order isn't going to have much effect, so I would suggest removing that.
I'd also suggest using .cap() instead of that complicated data-order-top thing you have, which looks complicated / delicate. Capping is built-in functionality for pie charts and row charts.
When using a model as a source to an entity, say gltf, is there a way we know the original size? Since the scale attribute works on relative size, it seems to be a trial an error to fit the model to our desired size. I tried using the geometry.getComputingBox() of the mesh of the model but it returns null. Wondering if there is a component that is available that lets us specify the scale in absolute terms.
Ah, figured it out.
var model = this.el.object3D;
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
gives you the size. then using the above any desired size can be set.
Created a simple component that can be conveniently used
AFRAME.registerComponent('resize', {
schema: {
axis: {
type: 'string',
default: 'x'
},
value: {
type: 'number',
default: 1
}
},
init: function() {
var el = this.el;
var data = this.data;
var model = el.object3D;
el.addEventListener('model-loaded', function(e) {
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
var x = size.x;
var y = size.y;
var z = size.z;
if(data.axis === 'x') {
var scale = data.value / x;
}
else if(data.axis === 'y') {
var scale = data.value / y;
}
else {
var scale = data.value / z;
}
el.setAttribute('scale', scale + ' ' + scale + ' ' + scale);
});
}
});
And it can be used as to proportionately resize the model with x axis length as 0.5
<a-entity resize='axis:x; value:0.5' gltf-model='#model`></a-entity>
(This would have come as a comment but as I don't have enough rep points this is coming as an answer.)
I found that the model doesn't have a size directly after the model-loaded event listener so I trigger the rescale from the update method. Funnily enough though if you don't have the model-loaded event listener then the size of the model will be 0 even after the first update is fired.
This is my variant of the above code with the difference being that the dimension is set in meters:
/**
* Scales the object proportionally to a set value given in meters.
*/
AFRAME.registerComponent("natural-size", {
schema: {
width: {
type: "number",
default: undefined, // meters
},
height: {
type: "number",
default: undefined, // meters
},
depth: {
type: "number",
default: undefined, // meters
},
},
init() {
this.el.addEventListener("model-loaded", this.rescale.bind(this));
},
update() {
this.rescale();
},
rescale() {
const el = this.el;
const data = this.data;
const model = el.object3D;
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize();
if (!size.x && !size.y && !size.z) {
return;
}
let scale = 1;
if (data.width) {
scale = data.width / size.x;
} else if (data.height) {
scale = data.height(size.y);
} else if (data.depth) {
scale = data.depth / size.y;
}
el.setAttribute("scale", `${scale} ${scale} ${scale}`);
},
remove() {
this.el.removeEventListener("model-loaded", this.rescale);
},
});
Then:
<a-entity natural-size='width:0.72' gltf-model='#model`></a-entity>
box.getSize has changed, I combined what I found here with what I found in another answer and noticed in the console to produce a more minimalist answer just to determine the size itself of a model:
getDimensions(object3d) {
// e.g., object3d = document.querySelector('#goban').object3D
var box = new THREE.Box3().setFromObject( object3d );
var x = box.max.x - box.min.x
var y = box.max.y - box.min.y
var z = box.max.z - box.min.z
return {x,y,z}
}
I'm trying find out a method or a way by which i can move handson tables scroll bar to specific row and column.
I tried using
selectCell (row: Number, col: Number, rows: Number, cols: Number, scrollToSelection: Boolean (Optional)) but it doesn't seems to work
here is the Fiddle link for it http://jsfiddle.net/hpfvc9bx/
$(document).ready(function () {
function createBigData() {
var rows = []
, i
, j;
for (i = 0; i < 1000; i++) {
var row = [];
for (j = 0; j < 6; j++) {
row.push(Handsontable.helper.spreadsheetColumnLabel(j) + (i + 1));
}
rows.push(row);
}
return rows;
}
var maxed = false
, resizeTimeout
, availableWidth
, availableHeight
, $window = $(window)
, $example1 = $('#example1');
var calculateSize = function () {
if(maxed) {
var offset = $example1.offset();
availableWidth = $window.width() - offset.left + $window.scrollLeft();
availableHeight = $window.height() - offset.top + $window.scrollTop();
$example1.width(availableWidth).height(availableHeight);
}
};
$window.on('resize', calculateSize);
var table = $example1.handsontable({
data: createBigData(),
colWidths: [55, 80, 80, 80, 80, 80, 80], //can also be a number or a function
rowHeaders: true,
colHeaders: true,
fixedColumnsLeft: 2,
fixedRowsTop: 2,
minSpareRows: 1,
stretchH: 'all',
contextMenu: true,
afterChange : function(changes){
console.log(changes);
}
});
$('.maximize').on('click', function () {
maxed = !maxed;
if(maxed) {
calculateSize();
}
else {
$example1.width(400).height(200);
}
$example1.handsontable('render');
});
$("#setSelectedRow").on('click',function(){
console.log(table);
table.select(9,3,12,6,true); // not working as it doesnt move scroll bar to specified column and range
})
function bindDumpButton() {
$('body').on('click', 'button[name=dump]', function () {
var dump = $(this).data('dump');
var $container = $(dump);
console.log('data of ' + dump, $container.handsontable('getData'));
});
}
bindDumpButton();
});
Can anyone please help me on this...
Thanks In advance...
You can move to specific cell using "selectCell" of handsontable.
You have use table.select(...) in $("#setSelectedRow").on('click',function(){....
It won't work as you are using table DOM element but you need to have "instance" of handsontable. That can be done as,
First Way:
var tblInstance = $example1.handsontable('getInstance');
And then apply "selectCell()" as
tblInstance.selectCell(rowNum, colNum);
Second Way:
Use $example1.handsontable("selectCell", rowNum, colNum);
Please refer to fiddle for the second way, in which :
$("#setSelectedRow").on('click',function(){
console.log(table);
$example1.handsontable("selectCell", 9, 5); // select 9th row's 5th column
})
Hope this will help you :)
I tried to find several places how this work but could not.
The requirement is to drill down by clicking on the slice of the pie to next level. I can get the onclick even but not sure how to get the value from the chart. Everywhere it is pointing to http://www.sitepen.com/blog/2008/05/27/dojo-charting-event-support-has-landed/ but nowhere any live demo is given. Till now i have managed to get the onclick.
chart.addSeries("Monthly Sales - 2010", chartData);
var h = chart.connectToPlot("default", function(o){
if(o.type == "onclick"){
alert("clicked!");
}
});
var store = new dojo.store.Memory({data: [
{ id: '2', value: 10, usedForDrillDown:'x' },
{ id: '3', value: 5, usedForDrillDown: 'y' },
{ id: '4', value: 8, usedForDrillDown:'z' }
]});
// adapter needed, because the chart uses the dojo.data API
var storeAdapter = new dojo.data.ObjectStore({
objectStore: store
});
var ds = new dojox.charting.DataSeries(
storeAdapter/*, { query: { needed if the store contains more than data points } }*/);
var chart = new dojox.charting.Chart("chart");
chart.addPlot("default", { type: "Pie" });
chart.addSeries("default", ds);
chart.connectToPlot("default", function(evt) {
if(evt.type == "onclick"){
var itm = evt.run.source.items[evt.index];
console.dir(itm);
}
});
chart.render();