Way to shorten / refactor multiple plugin values - javascript

I have some 15-20 highcharts on a single page (using a slider, 1-2 charts per slide), some bar charts, some column charts, some pie charts, with different display options. What I was using was having multiple methods inside my closure where I had methods like self.drawColumnChart(xPlotColor, yPlotColor, xPlotLabelSize, yPlotLabelSize, ...10 more arguments). Inside the same object I have methods like 'drawRevenueChart(), drawLossChart()' etc. drawRevenueChart() was calling self.drawColumnChart( with 15 arguments. As the number of charts grew, I ended up passing more and more arguments to self.drawColumnChart( so I thought I could refactor this by changing the drawRevenueChart() as
("$id").highcharts(
{chart: {
plotOptions: {
labelSize: '2em'
},
xAxis:{
labelSize: '1.3em',
formatter: function(){
return '% ' + this.value;
}
...and so on
}
})'
I don't need the self.drawColumnChart(xPlotColor, yPlotColor, xPlotLabelSize, yPlotLabelSize, ...10 more arguments) any more but I just passed that complexity to drawRevenueChart(). drawRevenueChart() used to be 2 lines long, but now it's 25 lines long. Same with drawLossChart(), it used to be 3 lines long, just calling self.drawColumnChart(, but it turned into a 15 line long method after refactor.
Can you guys think of any other way how I can refactor/shorten this? Maybe drawRevenueChart() calls self.drawChart("column", [plotOptions.labelSize: '2em', xAxis: {labelSize: '1.e em'}...
It just seems that I have to keep repeating
plotOptions: {
labelSize: '2em'
},
xAxis:{
labelSize: '1.3em',
all over my closure for each chart with different options. Is there a way to shorten this? I'm already using jQuery extend() to extend default chart options with custom options. It's all inside a closure. But regardless of how I refactor this, I find myself repeating the same lines with different options. Any ideas are welcome.
Update:
As requested by TrueBlueAussie:
it used to be:
myClosure{
var self = this;
self.drawColumnChart = function(selector, xPlotColour, yPlotColour, xAxisName, yAxisName, xPlotOptionsSize....10 more arguments)
{
$(selector).highcharts({
chart: {
type: 'column'
},
xPlot:{
style:{
color: xPlotColour
}
},
yPlot: {
labels: {
style:{
color: yPlotColour
}
}
},
xAxis:{
labels: {
name: xAxisName,
}
}
})
}
drawRevenueChart: function(data){
self.drawColumnChart("#chartid1", 'blue', 'red', 'profit', 'month', '1.2em', null, false, null....);
}
drawLossChart: function(data){
self.drawColumnChart("#chartid2", 'orange', 'yellow, 'loss', 'month', '2em' ...);
}
}
After refactor
drawRevenueChart: function(data){
$("#chartid1").highcharts({
chart: {
type: 'column'
},
xPlot:{
style:{
color: 'blue'
}
},
yPlot: {
labels: {
style:{
color: 'red'
}
}
},
xAxis:{
labels: {
name: 'profit',
}
}
});
}
drawLossChart: function(data){
$("chartid2").highcharts({
xplot:{
style:{
color: 'orange'
}
},
xAxis:{
labels:{
name: 'loss',
color: 'puke'
}
}
}
};
So I just moved the 3 level deep object setting from one method to another, no real gain.

Okay, that is clearer now. This is not really a code refactoring problem, so much as a data-refactoring problem. The only solution I can suggest is to find common data in the structures, store those branches as vars within your scope and $.extend() them together to build the final options structure.
e.g.
myClosure {
// Put any common settings in shared vars
var columnChart = {
chart: {
type: 'column'
}
// any other common properties for a column chart
};
var barChart = {
chart: {
type: 'barchart'
}
// any other common properties for a bar chart
}
var self = this;
self.drawColumnChart = function (selector, data) {
$(selector).highcharts($.extend({}, columnChart, data));
}
drawRevenueChart: function (data) {
self.drawColumnChart("#chartid1", {
xPlot: {
style: {
color: 'blue'
}
},
yPlot: {
labels: {
style: {
color: 'red'
}
}
},
xAxis: {
labels: {
name: 'month name',
}
}
});
}
drawLossChart: function (data) {
self.drawColumnChart("#chartid2", {
xPlot: {
style: {
color: 'orange'
}
},
yPlot: {
labels: {
style: {
color: 'yellow'
}
}
},
xAxis: {
labels: {
name: 'loss',
}
});
}
}
}
This has the advantage that each call is still readable, if you know the HighCharts option structure, and is therefore easier to maintain.
Unless you use a strongly typed JS language (like TypeScript), using functions with loads of parameters is human-error-prone so best avoided.

What I ended up doing is I created an object inside the plugin which handles all the highcharts options with a single drawChart function which takes an object. i.e.
var plugin = function(){
helper.drawChart({
legendEnabled: true,
selector: '#divID',
backgroundColor: '#FF0000', //etc all other options
});
var chartHelper = function(){
var defaults = {
_common:{
//common highcharts options
{
legend: false //etc
}
},
_bar:{
chart: 'bar', //all common bar options
},
get bar(){ return $.extend({}, _common, _bar); }
}
this.drawChart = function(options){
var default = {};
if (options.legendEnabled){
default.legend = true;
}
if (options.yLabelText){
default.yTitle = { text = options.yLabelText }
}//and other options here
$(options.selector).highchart($.extend({}, defaults.bar, default);
}
}
var helper = new chartHelper();
}
I have some 20 charts inside the plugin and this saved me some 600 lines. All the chart logic is inside the helper, it doesn't clutter the plugin code, I don't need to keep repeating .highcharts({ 20 different options }) inside each draw function, it's just once I need to do that now.

Related

Inefficient functions - how to assign colour to datapoints in Highchart

I am trying to create a progress bar for surveys. Depending on the progress, I would like to assign colors dynamically to each data point. I have managed this, but it seems that the functions takes ages to run. At least, that is my interpretation as I am new to much of this.
So my question is: How can I assign colors to the data points more effectively? I am not sure how to reference to the different colors within Highcharts if I create and store multiple colors in one function.
http://jsfiddle.net/oskjerv/v4Lx69Lv/
Hope my question makes sense to some/any of you.
All the best.
Highcharts.chart('container', {
chart: {
type: 'bar'
},
title: {
text: ''
},
xAxis: {
categories: [''],
visible:false,
},
yAxis: {
min: 0,
visible:false
},
legend: {
reversed: true,
enabled:false
},
plotOptions: {
series: {
stacking: 'normal',
animation:false
}
},
labels: {
enabled: false
},
tooltip: {
enabled: false
},
exporting:{
enabled:false
},
credits:{
enabled:false
},
series: [{
name: 'Del1',
data: [.20],
color: getColourFirst()
}, {
name: 'Del2',
data: [.20],
color: getColourSecond()
}, {
name: 'Del3',
data: [.20],
color: getColourThird()
},
{
name: 'Del4',
data: [.20],
color: getColourFourth()
},
{
name: 'Del4',
data: [.20],
color: getColourFifth()
}]
});
function getColourFirst(){
var colour1=['#D5D4D4'];
return colour1;
}
function getColourSecond(){
var colour2=['#D5D4D4'];
return colour2;
}
function getColourThird(){
var colour3=['#87bdd8'];
return colour3;
}
function getColourFourth(){
var colour4=['#87bdd8'];
return colour4;
}
function getColourFifth(){
var colour5=['#87bdd8'];
return colour5;
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<div id="container" style="min-width: 310px; max-width: 200px; height: 100px; margin: 0 auto"></div>
The main problem here is that you are saying color: ['#87bdd8']. This is not a valid format. In short you are looking for color: '#87bdd8' instead. See this updated JSFiddle for a demo, and see the Colors description for more detail on the syntax.
When it comes to how to solve the colors there are a million ways to do it, but I'd probably suggest something that uses an index instead of all separate unautomatable function names. For example:
myColors = ['#D5D4D4','#D5D4D4','#87bdd8','#87bdd8','#87bdd8'];
function getColor(index) {
return myColors[index%myColors.length];
}
Or see this JSFiddle demonstration.

javascript highcharts builder function

I am trying to make a function which will be building Highcharts charts dynamically based on parameters passed. I do it the following way:
function makeChart(name, title, series)
{
var options = {
chart: {
type: 'areaspline',
renderTo: name
},
credits: { enabled: false },
legend: { enabled: true },
title: {
text: title
},
xAxis: {
type: 'datetime'
},
yAxis: {
gridLineDashStyle: 'dot',
title: {
text: 'Quantity'
}
},
plotOptions: {
areaspline: {
animation: false,
stacking: '',
lineWidth: 1,
marker: { enabled: false }
}
},
series: [] //chart does not display except title. It will draw if I paste the data here manually
};
this.chart = new Highcharts.Chart(options);
for (index = 0; index < series.length; ++index) {
options.series[index] = {'name':series[index][0], 'data':series[index][1], 'color':series[index][2], 'fillOpacity': .3};
}
}
makeChart('container2', 'second chart', [['thisisname1', [20,21,22,23,24,25,26,27,28], '#d8d8d8']]);//calling function with test parameters
But everything I can see is the charts title. I guess the problem is in adding data to series array. I tried to add it with several ways but it did not work, although I see that the data has been added if I console.log(options.series). Any ideas how to fix that? Thank you.
Place this.chart = new Highcharts.Chart(options); after the for loop.
You're adding the data after the chart has been initialized, for it to work this way you need to tell HighCharts to redraw itself, easier option is to init after the loop. :)

ExtJS 5 Pie Chart Not Rendering Using Remote Store

I have a basic pie chart in ExtJS 5. The issue I am having is that the chart renders with a static JsonStore but won't render properly with a remote data.store?
Here is my code:
View (Chart)
Ext.define('APP.view.core.graphs.Countytotals', {
extend: 'Ext.Panel',
alias: 'widget.countytotalchart',
id: 'countyTotalsGraph',
width: 650,
initComponent: function() {
var me = this;
// Doesn't work?
var countyStore = Ext.create('APP.store.Countytotalsgraph');
// Works
var store = Ext.create('Ext.data.JsonStore', {
fields: ['COUNTY', 'AMOUNT'],
data: [{
'COUNTY': 'London',
'AMOUNT': 10.92
}, {
'COUNTY': 'Lancashire',
'AMOUNT': 6.61
}, {
'COUNTY': 'Kent',
'AMOUNT': 5.26
}, {
'COUNTY': 'West Yorkshire',
'AMOUNT': 4.52
}, {
'COUNTY': 'Nottinghamshire',
'AMOUNT': 4.01
}, {
'COUNTY': 'Other',
'AMOUNT': 68.68
}]
});
var chart = new Ext.chart.PolarChart({
width: '100%',
height: 500,
insetPadding: 50,
innerPadding: 20,
legend: {
docked: 'bottom'
},
listeners: {
afterrender: function (chart) {
if (chart.isVisible()) {
countyStore.load();
chart.redraw();
}
}
},
interactions: ['itemhighlight'],
store: countyStore,
series: [{
type: 'pie',
angleField: 'AMOUNT',
label: {
field: 'COUNTY',
display: 'outside',
calloutLine: {
length: 60,
width: 3
// specifying 'color' is also possible here
}
},
highlight: true,
tooltip: {
trackMouse: true,
renderer: function(storeItem, item) {
this.setHtml(storeItem.get('COUNTY') + ': ' + storeItem.get('AMOUNT') + '%');
}
}
}]
});
me.items = [chart];
this.callParent();
}
});
Store
Ext.define('APP.store.Countytotalsgraph', {
extend: 'Ext.data.Store',
model: 'APP.model.Countytotalsgraph',
autoLoad: false,
storeId: 'countyTotalsGraphStore',
proxy: {
type: 'ajax',
url : '/dashboard/countytotals',
method : 'POST',
reader: {
type: 'json',
rootProperty: 'data'
}
},
listeners: {
beforeload: function(store, eOpts) {
//if ( this.data.items.length ) {
//Ext.getCmp('optionsGrid').getView().refresh();
//}
store.proxy.extraParams = {
percentage: 'true'
}
}
}
});
Model
Ext.define('APP.model.Countytotalsgraph', {
extend: 'Ext.data.Model',
fields: ['COUNTY', 'AMOUNT']
});
This is how is renders with the static store:
This is how it renders with the remote store:
I am on the latest version of the GPL although the charts were built using Sencha CMD and the "sencha ant build" command in the sencha-charts directory.
Why does the static store display it (well still there is still an issue regarding the legend at the bottom) but the remote json not?
Iv'e tried to load the store after it the chart is rendered and is visible as I have seen a previous post regarding holding off on loading the store to give the chart time to render but this did not work:
listeners: {
afterrender: function (chart) {
if (chart.isVisible()) {
countyStore.load();
chart.redraw();
}
}
},
Thanks in advance :)
Nathan
Probably a bug in Ext.
The chart colors are set in Ext.chart.AbstractChart#updateColors. This is a "config" method, that is called automatically when setColors is called, and also from the constructor, when the config is initialized.
In your case, it is only called at construction time, before the remote store has been loaded; and it happens that polar series need to know the number of records in the store in order to know how many colors it must used (unlike other kind of charts that rely on number of axis or so).
Here's the code of that method:
updateColors: function (newColors) {
var me = this,
colors = newColors || (me.themeAttrs && me.themeAttrs.colors),
colorIndex = 0, colorCount = colors.length, i,
series = me.getSeries(),
seriesCount = series && series.length,
seriesItem, seriesColors, seriesColorCount;
if (colorCount) {
for (i = 0; i < seriesCount; i++) {
seriesItem = series[i];
// Ext.chart.series.Polar#themeColorCount uses store.getCount()
// so seriesColorCount will be 0
seriesColorCount = seriesItem.themeColorCount();
// ... hence seriesColor will be an empty array
seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
colorIndex += seriesColorCount;
seriesItem.updateChartColors(seriesColors);
}
}
me.refreshLegendStore();
}
You could probably get it working by creating the chart after the load event of the store, but that's kind of kinky given your usage is as intended, and the bug will probably get smashed in a coming release.
For now, a possible fix is to override the onRefresh of the chart, that is called, well, when the store is refreshed, and force colors to be updated at this time:
Ext.define(null, {
override: 'Ext.chart.PolarChart'
,onRefresh: function() {
this.callParent(arguments);
var colors = this.getColors();
if (colors) {
this.updateColors(colors);
}
}
});

highcharts; add arbitrary data which lasts when rendered

let's say I am generating chart json that might look something like this:
return jsonify({
chart: {
post_url: '/some/url/'
type: column
},
title: {
text: this is a chart
},
tooltip: {
shared: true
},
xAxis: {
categories: [x[0].strftime('%b %y') for x in arr]
},
plotOptions: {
column: {
stacking: normal
}
}
series: [{
data: [[x.thing for x in month].count(True) for month in arr]
}, {
data: [[x.thing for x in month].count(False) for month in arr]
}]
})
In setOptions, I do the following.
Highcharts.setOptions({
plotOptions: {
series: {
cursor: 'pointer',
point: {
events: {
click: function(el) {
$.post(this.chart.post_url + this.category, function(data) {
console.log(data);
});
}
}
}
}
}
});
However, this does not allow me to click and go to the post url, saying post_url is not defined. So I imagine that data is lost when the chart is rendered.
What's a way around this?
The this in a point.events.click referres to a point object and not a chart object. You can walk backwards to the chart configuration using this.series.chart.userOptions.chart.post_url like so:
point: {
events: {
click: function() {
alert(this.series.chart.userOptions.chart.post_url);
}
}
}
Here's an example fiddle.
If I understand correctly you want to have the series.points go to a URL when you click on them? You are missing an ending comma after your post_url definition and quotes around the type value. That being said I would set the URL in the series options and not the chart options. Then you might need to set it like:
series: [{
post_url: '/some/url/',
data: [[x.thing for x in month].count(True) for month in arr]
}, {
post_url: '/some/url/',
data: [[x.thing for x in month].count(False) for month in arr]
}]
Then in the click event:
events: {
click: function(el) {
$.post(this.post_url + this.category, function(data) {
console.log(data);
});
}
Demo.
If you still want to use the chart.post_url method you need to fix your typos. Demo.

jqPlot: possible to dynamically add a new series?

Is it possible to dynamically add a new series to an existing jqPlot object?
I have a jqPlot object that uses the AJAX data renderer to retrieve 2 series. This part works works fine.
Based on user input (and several parameters), I would like to be able to dynamically add or remove additional series to the chart (while keeping the two original).
Is this possible? Is it possible without having to retrieve the unchanged data for the original two lines again?
Alternatively, if this is not possible, are there any recommendations for a different charting library that can do this?
Yes it is, I just found out how to do this, and I found your question, and there was no answer, so I will provide mine. Now, this is probably not the most elegant way to do it, but it works.
$(document).ready( function () {
DataSeriesToPlot = [[[x1_1,y1_1],[x1_2,y1_2]],[[x2_1,y2_1],[x2_2,y2_2]],
[[x3_1,y3_1], [x3_2,y3_2]]];
AxesOptions = {
xaxis: {min: xmin, max: xmax},
yaxis: {min: ymin}
};
PlotTitle = 'PlotTitle',
PlotSeriesDefaults = {
showMarker: false,
shadow: false,
rendererOptions: {
smooth: true
}
};
PlotLegend = {
show: true,
labels: ['label1','label2','label3']
};
PlotSeriesOptions = [
{
linePattern: 'dashed',
color: '#f80202',
},
{
linePattern: 'dashed',
color: '#f80202',
},
{
color: '#f80202',
}
];
PlotVar = $.jqplot('Plotdiv', DataSeriesToPlot,
{
axes: AxesOptions,
title: PlotTitle,
seriesDefaults: PlotSeriesDefaults,
series: PlotSeriesOptions,
legend: PlotLegend
});
AddToPlot();
});
function AddToPlot(){
$("Plotdiv").empty();
DataSeriesToPlot.push([[x4_1,y4_1],[x4_2,y4_2]]);
PlotLegend.labels.push('label4');
PlotSeriesOptions.push({
linePattern: 'dashed',
color: '#ff6600',
});
PlotVar = $.jqplot('Plotdiv', DataSeriesToPlot,
{
axes: AxesOptions,
title: PlotTitle,
seriesDefaults: PlotSeriesDefaults,
series: PlotSeriesOptions,
legend: PlotLegend
});
}

Categories

Resources