In the extjs 4.1.1a code below is a working example of a line chart. Now I need to highlight a part of that chart on a given min and max timestamp.
{'xtype' : 'chart',
'store' : 'ChartData',
'height' : '100%',
'width' : '100%',
'legend' : {'position' : top},
'axes': [{
'title': 'Power',
'type': 'Numeric',
'position': 'left',
'fields': ['power']
},{
'title': 'Timestamp',
'type': 'Numeric',
'position': 'bottom',
'fields': ['timestamp'],
'minorTickSteps': 3
}],
'series': [{
'type': 'line',
'fill': true,
'axis' : 'left',
'xField': 'timestamp',
'yField': 'power'
}]}
I've searched the sencha forum and found nothing in particular that meets my requirements.
For now I managed to change the color of the points on the line chart with a custom renderer.
'renderer': function(sprite, record, attr, index, store) {
var item = store.getAt(index);
if(item != undefined && (item.get('timestamp') < startdate || item.get('timestamp') > enddate)){
return Ext.apply(attr, {'fill': '#00cc00', 'stroke-width': 3, 'radius': 4});
}else{
return Ext.apply(attr, {'fill': '#ff0000', 'stroke-width': 3, 'radius': 4});
}}
But I have not found a way to change the color below the line.
Any suggestions on that?
UPDATE - Working fine now
I implement a solution based on the answer given by Colombo.
doCustomDrawing: function () {: function (p){
var me = this, chart = me.chart;
if(chart.rendered){
var series = chart.series.items[0];
if (me.groupChain != null) {
me.groupChain.destroy();
me.groupChain = null;
}
me.groupChain = Ext.create('Ext.draw.CompositeSprite', {
surface: chart.surface
});
if(series != null && series.items != null){
var surface = chart.surface;
var pathV = 'M';
var first = true;
// need first and last x cooridnate
var mX = 0,hX = 0;
Ext.each(series.items, function(item){
var storeItem = item.storeItem,
pointX = item.point[0],
pointY = item.point[1];
// based on given startdate and enddate start collection path coordinates
if(!(storeItem.get('timestamp') < startdate || storeItem.get('timestamp') > enddate)){
if(hX<pointX){
hX = pointX;
}
if(first){
first = false;
mX = pointX;
pathV+= + pointX + ' ' + pointY;
}else{
pathV+= ' L' + pointX + ' ' + pointY;
}
}
});
var sprite = Ext.create('Ext.draw.Sprite', {
type: 'path',
fill: '#f00',
surface: surface,
// to draw a sprite with the area below the line we need the y coordinate of the x axe which is in my case items[1]
path : pathV + ' L'+ hX + ' ' + chart.axes.items[1].y + ' L'+ mX + ' ' + chart.axes.items[1].y + 'z'
});
me.groupChain.add(sprite);
me.groupChain.show(true);
}
}}
This looks really good and has the effect I was hoping for and in case you resize the container the new sprite is cleared from the chart. Thx to Colombo again.
This is possible to implement. Here is how I would do it.
1. Add a listener for afterrender event for series.
listeners: {
afterrender: function (p) {
this.doCustomDrawing();
},
scope: me
}
2. Create a CompositeSprite
doCustomDrawing: function () {
var me = this, chart = me.chart;
if (chart.rendered) {
var series = chart.series.items[0];
if (me.groupChain != null) {
me.groupChain.destroy();
me.groupChain = null;
}
me.groupChain = Ext.create('Ext.draw.CompositeSprite', {
surface: chart.surface
});
// Draw hilight here
Ext.each(series.items, function (item) {
var storeItem = item.storeItem,
pointX = item.point[0],
pointY = item.point[1];
//TODO: Create your new line sprite using pointX and pointY
// and add it to CompositeSprite me.groupChain
});
me.groupChain.show(true);
}
},
There is no "built-in" way to go about this. You are probably running into some difficulties looking for it because "highlighting" a line chart currently means something totally different in the ExtJS API. It normally refers to making the line and markers bold when you hover the mouse over a data series.
However your idea sounds interesting, I might need use this in a project I have coming up.
I don't have the time to work out the exact code right now but this could be done by creating a rectangular sprite and adding it to the chart's surface property.
You can do that even after the chart has been all rendered using the Ext.draw.Surface.add method as described here in the docs.
You will have to come up with logic for determining width and positioning, if I remember the chart API properly, you should be able to extract x coordinates (horizontal location) of individual records by fishing around in the items property of the Ext.chart.series.Line object inside the chart.
The height of the highlight should be easy though - just read the height of the chart.
Related
When mouse is moved, I want to update the current value field of datagrid for both the lines. Unfortunately, it is updating for signal line when mouse is hovered over a particular line as you can see here:
DEMO : https://stackblitz.com/edit/react-ts-7d79lt?file=stockchart%2Fstockchart.js
I kind of know it is because I'm attaching mouseOver callback to point property of plotOptions
plotOptions: {
series: {
// compare: 'percent',
showInNavigator: true,
dataGrouping: {
enabled: false,
},
point: {
events: {
mouseOver: (e) => {
const temp = [...stockGrid];
const stock = temp.find((x) => x.name === e.target.series.name);
if (stock) {
stock.currentValue = e.target.options.y;
setStockGrid(temp);
}
},
},
},
},
},
I tried attaching mouseOver to series also but I don't know how to extract right value with it.
I want to update both lines' current value simultaneously when mouse is moving anywhere in the chart.
2) If you look at the tooltip, I want to get rid of the outer rectangle from all tooltips. I tried setting tooltip.borderWidth to 0 , This removes the outer rectangle but it also removes tooltip connection line to vertical line. I need a line between vertical line (from line point to tooltip) and tooltip.
1) You can iterate through all chart series and check if any of points has the same x value as hovered one.
plotOptions: {
series: {
point: {
events: {
mouseOver: function() {
let hoveredPoint = this,
text = '';
hoveredPoint.series.chart.series.forEach(series => {
if (hoveredPoint.series.name != series.name) {
series.points.forEach(point => {
if (hoveredPoint.x === point.x) {
text += point.series.name + ': ' + point.y + ', ' + hoveredPoint.series.name + ': ' + hoveredPoint.y;
console.log(text)
}
})
}
})
},
}
}
},
},
Simplified demo:
https://jsfiddle.net/BlackLabel/75Lkw4qj/
2) Have you considered removing the inner border instead?
Demo:
https://stackblitz.com/edit/react-ts-6asjx2?file=stockchart%2Fstockchart.js
I defined a text area field in an extJS window as follows:
me.myTextArea = Ext.create({
xtype: 'textareafield',
width: 500,
height: 500,
editable: true,
selectOnFocus: false,
listeners: {
afterrender: function() {
this.focus(true);
let cursorPos = this.getValue().length;
this.selectText(cursorPos, cursorPos);
}
}
});
I added the text area field to a panel contained within a window, and I set the text area field as focus element. I prevented the text there to be selected after the textarea field's being rendered. I would like to add the following feature. On closing the window, I will be able to get the position the cursor has within the text area field. So far, my attemps at resolving the problem were withou success. I set up an alert as follows:
listeners: {
'close': function(me) {
alert(me.getCaretPos(cmp.myTextArea.getEl().getId()));
}
},
Now the function named "getCaretPos" is designed to get the cursor position. I did not invent the function, but I found in on the net. Haplessly, the function does not work, it always returns -1:
getCaretPos: function(id){
var el = document.getElementById(id);
var rng, ii=-1;
if(typeof el.selectionStart=="number") {
ii=el.selectionStart;
} else if (document.selection && el.createTextRange){
rng=document.selection.createRange();
rng.collapse(true);
rng.moveStart("character", -el.value.length);
ii=rng.text.length;
}
return ii;
}
Especially, I do not undertsand, why "el.selectionStart" is always undefined in the function. I would be very glad if somebody could help me in resolving this mystery.
In this FIDDLE, I have created a custometextarea using extend:Ext.form.field.TextArea and putted some custom method. I hope this will help/guide you to achieve your requirements.
I have checked this code is working in ExtJS 4.x and later.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('CustomeTextArea', {
extend: 'Ext.form.field.TextArea',
xtype: 'customtextarea',
inputTypeSelectionSupported: /text|password|search|tel|url/i,
selectDir: {
b: 'backward',
back: 'backward',
f: 'forward'
},
/*
* this event will call get the cursoe postion inside of field
* #return {NUMBER}
*/
getCaretPos: function () {
var dom = this.inputEl.dom,
pos, selection;
if (this.inputTypeSelectionSupported.test(dom.type)) {
pos = dom.selectionStart;
selection = (typeof pos !== 'number') && this.getTextSelection();
if (selection) {
pos = selection[0];
}
} else {
Ext.raise('Input type of "' + dom.type + '" does not support selectionStart');
}
return pos;
},
/*
* this event will call selectText event
* #params {NUMBER} pos
*/
setCaretPos: function (pos) {
this.selectText(pos, pos);
},
/*
* #params {NUMBER} start
* #params {NUMBER} end
* #params {STRING} direction to select it is optional
*/
selectText: function (start, end, direction) {
var me = this,
range,
dom = me.inputEl.dom,
len;
if (dom && me.inputTypeSelectionSupported.test(dom.type)) {
start = start || 0;
len = dom.value.length;
if (end === undefined) {
end = len;
}
direction = me.selectDir[direction] || direction || 'forward';
if (dom.setSelectionRange) {
dom.setSelectionRange(start, end, direction);
} else if (dom.createTextRange) {
range = dom.createTextRange();
range.moveStart('character', start);
range.moveEnd('character', -(len - end));
range.select();
}
} else if (!me.inputTypeSelectionSupported.test(dom.type)) {
Ext.raise('Input type of "' + dom.type + '" does not support setSelectionRange');
}
return me;
},
//This event will select the text inside of textfield/textarea
getTextSelection: function () {
var dom = this.inputEl.dom;
if (this.inputTypeSelectionSupported.test(dom.type)) {
return [
dom.selectionStart,
dom.selectionEnd,
dom.selectionDirection
];
} else {
Ext.raise('Input type of "' + this.dom.type + '" does not support selectionStart, selectionEnd and selectionDirection');
return [];
}
}
});
Ext.create('Ext.window.Window', {
title: 'cursor position',
height: 200,
width: 400,
layout: 'fit',
items: [{
xtype: 'customtextarea',
margin: 5,
grow: true,
name: 'message',
fieldLabel: 'Message',
labelAlign: 'top',
value: 'How can I retrieve the cursor position in an ExtJS text area field?',
anchor: '100%',
listeners: {
afterrender: function (cmp) {
Ext.defer(function () {
cmp.focus(false); //if you pass true then it will automatically select text inside of field
let cursorPos = cmp.getValue().length;
cmp.selectText(0, cursorPos - 5, 'b');
}, 50)
}
}
}],
listeners: {
beforeclose: function (win) {
var textArea = win.down('customtextarea'),
pos = textArea.getCaretPos();
Ext.Msg.alert('Success', `This is your cursor postion <b>${pos}</b>`)
}
}
}).show();
}
});
I'm using the grid from http://ui-grid.info/ in a project. I've created a hierarchical grid, which works nicely, but when I do an export, it only exports the data from the top-level grid.
This is by design and is standard functionality for the grid, so there's no point in me putting up any example code. Any example from http://ui-grid.info/docs/#/tutorial will do.
Is there a way to export the subgrid (preferably both the main grid AND the subgrid together as they appear in the grid)?
Sadly the answer is no.
As you can see here the function getData iterates through all rows and then through all columns, adding to an array of extractedFields the columns to be extracted and aggregating those in an array of extractedRows.
This means that no data, other than what's defined in gridOptions' columnDef will be read, converted and extracted.
By design, subgrid information are stored inside a property of any row entity's subGridOptions but this property is never accessed inside of the exporter feature.
The motivation behind this behaviour is that expandable grid feature is still at an alpha stage, so, supporting this in other features is not a compelling priority.
Furthermore, adding subgrid to a CSV could be quite hard to design, if we wanted to provide a general solution (for example I don't even think it would be compliant to CSV standard if you had different number of columns in the main grid and in subgrids).
That said, ui-grid is an open source project, so, if you have a working design in mind, feel free to open a discussion about it on the project gitHub page or, even better, if you can design a working (and tested) solution and create a pull request, even better!
I managed to get it working, although if I had the time I would do it a bit better than what I've done by actually creating a branch of the code and doing it properly, but given time constraints, what I've got is working nicely.
FYI, here's the way I ended up getting it to do what I wanted:
In my grid options, I turned off the CSV export options in the grid menu (because I've only implemented the changes for PDF).
I made a copy of exporter.js, named it custom.exporter.js and changed my reference to point to the new file.
In custom.exporter.js, I made a copy of the getData function and named it getGridRows. getGridRows is the same as getData, except it just returns the rows object without all the stuff that gets the columns and so on. For now, I'm coding it to work with a known set of columns, so I don't need all that.
I modified the pdfExport function to be as follows:
pdfExport: function (grid, rowTypes, colTypes) {
var self = this;
var exportData = self.getGridRows(grid, rowTypes, colTypes);
var docContent = [];
$(exportData).each(function () {
docContent.push(
{
table: {
headerRows: 1,
widths: [70, 80, 150, 180],
body: [
[{ text: 'Job Raised', bold: true, fillColor: 'lightgray' }, { text: 'Job Number', bold: true, fillColor: 'lightgray' }, { text: 'Client', bold: true, fillColor: 'lightgray' }, { text: 'Job Title', bold: true, fillColor: 'lightgray' }],
[formattedDateTime(this.entity.JobDate,false), this.entity.JobNumber, this.entity.Client, this.entity.JobTitle],
]
}
});
var subGridContentBody = [];
subGridContentBody.push([{ text: 'Defect', bold: true, fillColor: 'lightgray' }, { text: 'Vendor', bold: true, fillColor: 'lightgray' }, { text: 'Status', bold: true, fillColor: 'lightgray' }, { text: 'Sign off', bold: true, fillColor: 'lightgray' }]);
$(this.entity.Defects).each(function () {
subGridContentBody.push([this.DefectName, this.DefectVendor, this.DefectStatus, '']);
});
docContent.push({
table: {
headerRows: 1,
widths: [159, 150, 50, 121],
body: subGridContentBody
}
});
docContent.push({ text: '', margin: 15 });
});
var docDefinition = {
content: docContent
}
if (self.isIE()) {
self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
} else {
pdfMake.createPdf(docDefinition).open();
}
}
No, there is no direct way to export subgrid. rather you can create youur own json data to generate csv file.
Please check the below code
function jsonToCsvConvertor(JSONData, reportTitle) {
//If JSONData is not an object then JSON.parse will parse the JSON string in an Object
var arrData = typeof JSONData !== 'object' ? JSON.parse(JSONData) : JSONData,
csv = '',
row,
key1,
i,
subGridData;
//Set Report title in first row or line
csv += reportTitle + '\r\n\n';
row = '';
for (key1 in arrData[0]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += key1 + ',';
}
}
csv += row + '\r\n';
for (i = 0; i < arrData.length; i++) {
row = '';
subGridData = '';
for (key1 in arrData[i]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += '"' + arrData[i][key1] + '",';
}
else if(key1 === 'subGridOptions'){
//csv += row + '\r\n';
subGridData = writeSubGridData(arrData[i][key1].data);
}
}
csv += row + '\r\n';
csv = subGridData ? csv + subGridData + '\r\n' : csv;
}
if (csv === '') {
console.log('Invalid data');
}
return csv;
}
//Generates subgrid Data to exportable form
function writeSubGridData(subgridData){
var j,
key2,
csv = '',
row = '';
for (key2 in subgridData[0]){
if(key2 !== '$$hashKey'){
row += key2 + ',';
}
}
csv = row + '\r\n';
for (j=0; j < subgridData.length ; j++){
row = '';
for(key2 in subgridData[j]){
if(key2 !== '$$hashKey'){
row += '"' + subgridData[j][key2]+ '",';
}
}
csv += row + '\r\n';
}
return csv;
}
jsonToCsvConvertor(exportData, 'New-Report');
I have ExtJs 4 Area chart with Time serie. I'd like user to be able to horizontally select part of chart and then obtain higher density data from server adequately. Problem is I can't get boundary dates from selection. I've got:
var chart = Ext.create('Ext.chart.Chart', {
store: store,
enableMask: true,
mask: 'horizontal',
listeners: {
select: {
fn: function(me, selection) {
console.log(arguments); // selection = Object { height: 218, width: 117, x: 665, y: 123 }
}
},
...
But select listener provides only pixel data. Is there some way to get boundary axis data (e.g. { from: 2013-08-01, to: 2013-08-20 } or some way to unproject pixels to values? I'm desperade I would say it's such a basic thing but can't find solution anywhere. Thanks in advance.
Well.. it probably doesn't exists a method for this. After digging into source code I've utilized lines from chart.setZoom() method to create function for manual unprojecting of mask selection to X axis data:
var unprojectXAxis = function(chart, selection) {
zoomArea = {
x : selection.x - chart.el.getX(),
width : selection.width
};
xScale = chart.chartBBox.width,
zoomer = {
x : zoomArea.x / xScale,
width : zoomArea.width / xScale
}
ends = chart.axes.items[0].calcEnds();
from = (ends.to - ends.from) * zoomer.x + ends.from;
to = (ends.to - ends.from) * zoomer.width + from;
return { from: new Date(from), to: new Date(to) };
}
I am using a the following code to draw a rectangle on the map to a random lat. & long. based on a city name when a find button is clicked. The problem is if you do not put a different city name in and click the find button the program will draw a second rectangle on the cities original lat. & long. I either need to clear the map and redraw the rectangle or something not quite sure so a second rectangle can not be drawn. Here is the code I am using.
function codeAddress() {
var address = document.getElementById("gadres").value;
if(address=='') {
alert("Address can not be empty!");
return;
}
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
document.getElementById('lat').value=results[0].geometry.location.lat().toFixed(6);
document.getElementById('lng').value=results[0].geometry.location.lng().toFixed(6);
var latlong = "(" + results[0].geometry.location.lat().toFixed(6) + " , " +
+ results[0].geometry.location.lng().toFixed(6) + ")";
map.setZoom(15);
var mapcenter = map.getCenter();
var maplat = mapcenter.lat();
var maplng = mapcenter.lng();
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(maplat - 0.002, maplng - 0.002),
new google.maps.LatLng(maplat + 0.002, maplng + 0.002)
);
var rectangle = new google.maps.Rectangle({
bounds: bounds,
draggable:true,
editable: true,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 4,
fillColor: '#FF0000',
fillOpacity: 0.30,
});
rectangle.setMap(map);
google.maps.event.addListener(rectangle, 'bounds_changed', function() {
document.getElementById('coords').value = rectangle.getBounds();
});
} else {
alert("Lat and long cannot be found.");
}
});
}
all help greatly appreciated.
I haven't tried this yet but perhaps you can store your bounds to an array then use a loop statement for that array to check if there is an equal bounds using the equals() method
https://developers.google.com/maps/documentation/javascript/reference#LatLngBounds