@@ -506,32 +506,47 @@ var Animator = (function() { | |||
return pathComponents; | |||
}, | |||
verticalLine: function(xLine, newX, oldX) { | |||
translate: function(obj, oldCoord, newCoord, duration) { | |||
return [ | |||
{unit: xLine, array: [0], index: 0}, | |||
{transform: `${ newX }, 0`}, | |||
MARKER_LINE_ANIM_DUR, | |||
{unit: obj, array: [0], index: 0}, | |||
{transform: newCoord.join(', ')}, | |||
duration, | |||
STD_EASING, | |||
"translate", | |||
{transform: `${ oldX }, 0`} | |||
{transform: oldCoord.join(', ')} | |||
]; | |||
}, | |||
verticalLine: function(xLine, newX, oldX) { | |||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR); | |||
}, | |||
horizontalLine: function(yLine, newY, oldY) { | |||
return [ | |||
{unit: yLine, array: [0], index: 0}, | |||
{transform: `0, ${ newY }`}, | |||
MARKER_LINE_ANIM_DUR, | |||
STD_EASING, | |||
"translate", | |||
{transform: `0, ${ oldY }`} | |||
]; | |||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR); | |||
} | |||
}; | |||
return Animator; | |||
})(); | |||
// export function animateXLines(animator, lines, oldX, newX) { | |||
// // this.xAxisLines.map((xLine, i) => { | |||
// return lines.map((xLine, i) => { | |||
// return animator.verticalLine(xLine, newX[i], oldX[i]); | |||
// }); | |||
// } | |||
// export function animateYLines(animator, lines, oldY, newY) { | |||
// // this.yAxisLines.map((yLine, i) => { | |||
// lines.map((yLine, i) => { | |||
// return animator.horizontalLine(yLine, newY[i], oldY[i]); | |||
// }); | |||
// } | |||
// Leveraging SMIL Animations | |||
const EASING = { | |||
@@ -1026,10 +1041,8 @@ class BaseChart { | |||
title = '', | |||
subtitle = '', | |||
colors = [], | |||
summary = [], | |||
is_navigable = 0, | |||
has_legend = 0, | |||
type = '', | |||
@@ -1043,46 +1056,26 @@ class BaseChart { | |||
this.subtitle = subtitle; | |||
this.data = data; | |||
this.oldData = Object.assign({}, data); | |||
this.specific_values = data.specific_values || []; | |||
this.summary = summary; | |||
this.is_navigable = is_navigable; | |||
if(this.is_navigable) { | |||
this.current_index = 0; | |||
} | |||
this.has_legend = has_legend; | |||
this.setColors(colors, type); | |||
this.set_margins(height); | |||
this.setupConfiguration(arguments[0]); | |||
} | |||
get_different_chart(type) { | |||
if(type === this.type) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
setupConfiguration(args) { | |||
// Make a this.config, that has stuff like showTooltip, | |||
// showLegend, which then all functions will check | |||
this.setColors(args.colors, args.type); | |||
this.set_margins(args.height); | |||
if(!COMPATIBLE_CHARTS[this.type].includes(type)) { | |||
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type); | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'change_chart_type(type)' | |||
// that will update only the required elements, but for now ... | |||
return new Chart({ | |||
parent: this.raw_chart_args.parent, | |||
title: this.title, | |||
data: this.raw_chart_args.data, | |||
type: type, | |||
height: this.raw_chart_args.height, | |||
colors: use_color ? this.colors : undefined | |||
}); | |||
this.config = { | |||
showTooltip: 1, | |||
showLegend: 1, | |||
isNavigable: 0 | |||
}; | |||
} | |||
setColors(colors, type) { | |||
@@ -1101,58 +1094,81 @@ class BaseChart { | |||
} | |||
set_margins(height) { | |||
this.base_height = height; | |||
this.baseHeight = height; | |||
this.height = height - 40; | |||
this.translate_x = 60; | |||
this.translate_y = 10; | |||
} | |||
setup() { | |||
validate(){ | |||
if(!this.parent) { | |||
console.error("No parent element to render on was provided."); | |||
return; | |||
return false; | |||
} | |||
if(this.validate_and_prepare_data()) { | |||
this.bind_window_events(); | |||
this.refresh(true); | |||
if(!this.validateAndPrepareData()) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
validate_and_prepare_data() { | |||
validateAndPrepareData() { | |||
return true; | |||
} | |||
bind_window_events() { | |||
window.addEventListener('resize', () => this.refresh()); | |||
window.addEventListener('orientationchange', () => this.refresh()); | |||
setup() { | |||
if(this.validate()) { | |||
this._setup(); | |||
} | |||
} | |||
refresh(init=false) { | |||
// TODO: no init! | |||
this.setup_base_values(); | |||
this.set_width(); | |||
_setup() { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
this.setup_container(); | |||
this.setupLayers(); | |||
this.setupEmptyValues(); | |||
// this.setupComponents(); | |||
this.setup_values(); | |||
this.setup_utils(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.draw(true); | |||
} | |||
this.renderComponents(init); | |||
this.make_tooltip(); | |||
draw(init=false) { | |||
// (everything, layers, groups, units) | |||
this.setWidth(); | |||
if(this.summary.length > 0) { | |||
this.show_custom_summary(); | |||
} else { | |||
this.show_summary(); | |||
} | |||
// dependent on width >.<, how can this be decoupled | |||
this.setupComponents(); | |||
if(this.is_navigable) { | |||
this.setup_navigation(init); | |||
} | |||
this.makeChartArea(); | |||
this.makeLayers(); | |||
this.renderComponents(); // with zero values | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
if(init) this.update(this.data); | |||
} | |||
set_width() { | |||
update(data, animate=true) { | |||
this.oldData = Object.assign({}, this.data); | |||
this.data = this.prepareNewData(data); | |||
this.calculateValues(); | |||
this.updateComponents(animate); | |||
} | |||
prepareNewData(newData) { | |||
// handle all types of passed data? | |||
return newData; | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize', () => this.draw()); | |||
window.addEventListener('orientationchange', () => this.draw()); | |||
} | |||
setWidth() { | |||
let special_values_width = 0; | |||
let char_width = 8; | |||
this.specific_values.map(val => { | |||
@@ -1165,9 +1181,16 @@ class BaseChart { | |||
this.width = this.base_width - this.translate_x * 2; | |||
} | |||
setup_base_values() {} | |||
setupConstants() {} | |||
setupEmptyValues() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
setup_container() { | |||
makeContainer() { | |||
this.container = $$1.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
@@ -1182,35 +1205,48 @@ class BaseChart { | |||
this.chart_wrapper = this.container.querySelector('.frappe-chart'); | |||
this.stats_wrapper = this.container.querySelector('.graph-stats-container'); | |||
this.make_chart_area(); | |||
this.make_draw_area(); | |||
} | |||
make_chart_area() { | |||
makeChartArea() { | |||
this.svg = makeSVGContainer( | |||
this.chart_wrapper, | |||
'chart', | |||
this.base_width, | |||
this.base_height | |||
this.baseWidth, | |||
this.baseHeight | |||
); | |||
this.svg_defs = makeSVGDefs(this.svg); | |||
return this.svg; | |||
} | |||
make_draw_area() { | |||
this.draw_area = makeSVGGroup( | |||
this.drawArea = makeSVGGroup( | |||
this.svg, | |||
this.type + '-chart', | |||
`translate(${this.translate_x}, ${this.translate_y})` | |||
); | |||
} | |||
setupLayers() {} | |||
setup_values() {} | |||
setup_utils() {} | |||
make_tooltip() { | |||
makeLayers() { | |||
this.components.forEach((component) => { | |||
component.layer = this.makeLayer(component.layerClass); | |||
}); | |||
} | |||
calculateValues() {} | |||
renderComponents() { | |||
this.components.forEach(c => { | |||
c.store = c.make(...c.makeArgs); | |||
c.layer.textContent = ''; | |||
c.store.forEach(element => {c.layer.appendChild(element);}); | |||
}); | |||
} | |||
updateComponents() { | |||
// this.components.forEach((component) => { | |||
// // | |||
// }); | |||
} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chart_wrapper, | |||
colors: this.colors | |||
@@ -1231,8 +1267,11 @@ class BaseChart { | |||
this.stats_wrapper.appendChild(stats); | |||
}); | |||
} | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
if(this.is_navigable) return; | |||
setup_navigation(init=false) { | |||
this.make_overlay(); | |||
if(init) { | |||
@@ -1268,38 +1307,126 @@ class BaseChart { | |||
on_down_arrow() {} | |||
on_enter_key() {} | |||
updateData() {} | |||
getDataPoint() {} | |||
updateCurrentDataPoint() {} | |||
makeLayer(className, transform='') { | |||
return makeSVGGroup(this.draw_area, className, transform); | |||
return makeSVGGroup(this.drawArea, className, transform); | |||
} | |||
get_different_chart(type) { | |||
if(type === this.type) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
if(!COMPATIBLE_CHARTS[this.type].includes(type)) { | |||
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type); | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'change_chart_type(type)' | |||
// that will update only the required elements, but for now ... | |||
return new Chart({ | |||
parent: this.raw_chart_args.parent, | |||
title: this.title, | |||
data: this.raw_chart_args.data, | |||
type: type, | |||
height: this.raw_chart_args.height, | |||
colors: use_color ? this.colors : undefined | |||
}); | |||
} | |||
} | |||
class AxisChart extends BaseChart { | |||
constructor(args) { | |||
super(args); | |||
this.xAxisLabels = this.data.labels || []; | |||
this.y = this.data.datasets || []; | |||
this.is_series = args.is_series; | |||
this.format_tooltip_y = args.format_tooltip_y; | |||
this.format_tooltip_x = args.format_tooltip_x; | |||
this.zero_line = this.height; | |||
} | |||
validate_and_prepare_data() { | |||
validateAndPrepareData() { | |||
this.xAxisLabels = this.data.labels || []; | |||
this.y = this.data.datasets || []; | |||
this.y.forEach(function(d, i) { | |||
d.index = i; | |||
}, this); | |||
return true; | |||
} | |||
setupEmptyValues() { | |||
this.yAxisPositions = [this.height, this.height/2, 0]; | |||
this.yAxisLabels = ['0', '5', '10']; | |||
this.xPositions = [0, this.width/2, this.width]; | |||
this.xAxisLabels = ['0', '5', '10']; | |||
} | |||
setupComponents() { | |||
let self = this; | |||
this.yAxis = { | |||
layerClass: 'y axis', | |||
layer: undefined, | |||
make: self.makeYLines, | |||
makeArgs: [self.yAxisPositions, self.yAxisLabels, | |||
self.width, self.y_axis_mode], | |||
store: [], //this.yAxisLines | |||
animate: self.animateYLines | |||
}; | |||
this.xAxis = { | |||
layerClass: 'x axis', | |||
layer: undefined, | |||
make: self.makeXLines, | |||
makeArgs: [self.xPositions, self.xAxisLabels], | |||
store: [], //this.xAxisLines | |||
animate: self.animateXLines | |||
}; | |||
this.yMarkerLines = { | |||
// layerClass: 'y marker axis', | |||
// layer: undefined, | |||
// make: makeYMarkerLines, | |||
// makeArgs: [this.yMarkerPositions, this.yMarker], | |||
// store: [], | |||
// animate: animateYMarkerLines | |||
}; | |||
this.xMarkerLines = { | |||
// layerClass: 'x marker axis', | |||
// layer: undefined, | |||
// make: makeXMarkerLines, | |||
// makeArgs: [this.yMarkerPositions, this.xMarker], | |||
// store: [], | |||
// animate: animateXMarkerLines | |||
}; | |||
// Marker Regions | |||
// Indexed according to dataset | |||
this.dataUnits = { | |||
layerClass: 'y marker axis', | |||
layer: undefined, | |||
// make: makeXLines, | |||
// makeArgs: [this.xPositions, this.xAxisLabels], | |||
// store: [], | |||
// animate: animateXLines, | |||
indexed: 1 | |||
}; | |||
this.components = [ | |||
this.yAxis, | |||
// this.xAxis, | |||
// this.yMarkerLines, | |||
// this.xMarkerLines, | |||
// this.dataUnits, | |||
]; | |||
} | |||
setup_values() { | |||
this.data.datasets.map(d => { | |||
d.values = d.values.map(val => (!isNaN(val) ? val : 0)); | |||
@@ -1310,7 +1437,6 @@ class AxisChart extends BaseChart { | |||
setup_x() { | |||
this.set_avg_unit_width_and_x_offset(); | |||
if(this.xPositions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
} | |||
@@ -1376,39 +1502,39 @@ class AxisChart extends BaseChart { | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier); | |||
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
} | |||
setupLayers() { | |||
super.setupLayers(); | |||
// setupLayers() { | |||
// super.setupLayers(); | |||
// For markers | |||
this.y_axis_group = this.makeLayer('y axis'); | |||
this.x_axis_group = this.makeLayer('x axis'); | |||
this.specific_y_group = this.makeLayer('specific axis'); | |||
// // For markers | |||
// this.y_axis_group = this.makeLayer('y axis'); | |||
// this.x_axis_group = this.makeLayer('x axis'); | |||
// this.specific_y_group = this.makeLayer('specific axis'); | |||
// For Aggregation | |||
// this.sumGroup = this.makeLayer('data-points'); | |||
// this.averageGroup = this.makeLayer('chart-area'); | |||
// // For Aggregation | |||
// // this.sumGroup = this.makeLayer('data-points'); | |||
// // this.averageGroup = this.makeLayer('chart-area'); | |||
this.setupPreUnitLayers && this.setupPreUnitLayers(); | |||
// this.setupPreUnitLayers && this.setupPreUnitLayers(); | |||
// For Graph points | |||
this.svg_units_groups = []; | |||
this.y.map((d, i) => { | |||
this.svg_units_groups[i] = this.makeLayer( | |||
'data-points data-points-' + i); | |||
}); | |||
} | |||
// // For Graph points | |||
// this.svg_units_groups = []; | |||
// this.y.map((d, i) => { | |||
// this.svg_units_groups[i] = this.makeLayer( | |||
// 'data-points data-points-' + i); | |||
// }); | |||
// } | |||
renderComponents(init) { | |||
this.makeYLines(this.yAxisPositions, this.yAxisLabels); | |||
this.makeXLines(this.xPositions, this.xAxisLabels); | |||
this.draw_graph(init); | |||
// this.make_y_specifics(this.yAnnotationPositions, this.specific_values); | |||
} | |||
// renderComponents(init) { | |||
// this.makeYLines(this.yAxisPositions, this.yAxisLabels); | |||
// this.makeXLines(this.xPositions, this.xAxisLabels); | |||
// this.draw_graph(init); | |||
// // this.make_y_specifics(this.yAnnotationPositions, this.specific_values); | |||
// } | |||
makeXLines(positions, values) { | |||
let [start_at, height, text_start_at, | |||
@@ -1451,14 +1577,36 @@ class AxisChart extends BaseChart { | |||
}); | |||
} | |||
makeYLines(positions, values) { | |||
// makeYLines(positions, values) { | |||
// let [width, text_end_at, axis_line_class, | |||
// start_at] = getYLineProps(this.width, this.y_axis_mode); | |||
// this.yAxisLines = []; | |||
// this.y_axis_group.textContent = ''; | |||
// values.map((value, i) => { | |||
// let yLine = makeYLine( | |||
// start_at, | |||
// width, | |||
// text_end_at, | |||
// value, | |||
// 'y-value-text', | |||
// axis_line_class, | |||
// positions[i], | |||
// (value === 0 && i !== 0) // Non-first Zero line | |||
// ); | |||
// this.yAxisLines.push(yLine); | |||
// this.y_axis_group.appendChild(yLine); | |||
// }); | |||
// } | |||
makeYLines(positions, values, totalWidth, mode) { | |||
let [width, text_end_at, axis_line_class, | |||
start_at] = getYLineProps(this.width, this.y_axis_mode); | |||
start_at] = getYLineProps(totalWidth, mode); | |||
this.yAxisLines = []; | |||
this.y_axis_group.textContent = ''; | |||
values.map((value, i) => { | |||
let yLine = makeYLine( | |||
// this.yAxisLines = []; | |||
// this.y_axis_group.textContent = ''; | |||
return values.map((value, i) => { | |||
return makeYLine( | |||
start_at, | |||
width, | |||
text_end_at, | |||
@@ -1468,8 +1616,8 @@ class AxisChart extends BaseChart { | |||
positions[i], | |||
(value === 0 && i !== 0) // Non-first Zero line | |||
); | |||
this.yAxisLines.push(yLine); | |||
this.y_axis_group.appendChild(yLine); | |||
// this.yAxisLines.push(yLine); | |||
// this.y_axis_group.appendChild(yLine); | |||
}); | |||
} | |||
@@ -1512,14 +1660,14 @@ class AxisChart extends BaseChart { | |||
}, 350); | |||
} | |||
setup_navigation(init) { | |||
setupNavigation(init) { | |||
if(init) { | |||
// Hack: defer nav till initial updateData | |||
setTimeout(() => { | |||
super.setup_navigation(init); | |||
super.setupNavigation(init); | |||
}, 500); | |||
} else { | |||
super.setup_navigation(init); | |||
super.setupNavigation(init); | |||
} | |||
} | |||
@@ -1850,7 +1998,7 @@ class BarChart extends AxisChart { | |||
this.overlay = unit.cloneNode(); | |||
this.overlay.style.fill = '#000000'; | |||
this.overlay.style.opacity = '0.4'; | |||
this.draw_area.appendChild(this.overlay); | |||
this.drawArea.appendChild(this.overlay); | |||
} | |||
bind_overlay() { | |||
@@ -1926,7 +2074,7 @@ class LineChart extends AxisChart { | |||
this.paths_groups = []; | |||
this.y.map((d, i) => { | |||
this.paths_groups[i] = makeSVGGroup( | |||
this.draw_area, | |||
this.drawArea, | |||
'path-group path-group-' + i | |||
); | |||
}); | |||
@@ -2020,29 +2168,27 @@ class PercentageChart extends BaseChart { | |||
this.setup(); | |||
} | |||
make_chart_area() { | |||
makeChartArea() { | |||
this.chart_wrapper.className += ' ' + 'graph-focus-margin'; | |||
this.chart_wrapper.style.marginTop = '45px'; | |||
this.stats_wrapper.className += ' ' + 'graph-focus-margin'; | |||
this.stats_wrapper.style.marginBottom = '30px'; | |||
this.stats_wrapper.style.paddingTop = '0px'; | |||
} | |||
make_draw_area() { | |||
this.chart_div = $$1.create('div', { | |||
this.chartDiv = $$1.create('div', { | |||
className: 'div', | |||
inside: this.chart_wrapper | |||
}); | |||
this.chart = $$1.create('div', { | |||
className: 'progress-chart', | |||
inside: this.chart_div | |||
inside: this.chartDiv | |||
}); | |||
} | |||
setupLayers() { | |||
this.percentage_bar = $$1.create('div', { | |||
this.percentageBar = $$1.create('div', { | |||
className: 'progress', | |||
inside: this.chart | |||
}); | |||
@@ -2089,7 +2235,7 @@ class PercentageChart extends BaseChart { | |||
this.slice_totals.map((total, i) => { | |||
let slice = $$1.create('div', { | |||
className: `progress-bar`, | |||
inside: this.percentage_bar, | |||
inside: this.percentageBar, | |||
styles: { | |||
background: this.colors[i], | |||
width: total*100/this.grand_total + "%" | |||
@@ -2116,7 +2262,7 @@ class PercentageChart extends BaseChart { | |||
}); | |||
} | |||
show_summary() { | |||
renderLegend() { | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : this.labels; | |||
this.legend_totals.map((d, i) => { | |||
@@ -2228,7 +2374,7 @@ class PieChart extends BaseChart { | |||
const curPath = this.makeArcPath(curStart,curEnd); | |||
let slice = makePath(curPath, 'pie-path', 'none', this.colors[i]); | |||
slice.style.transition = 'transform .3s;'; | |||
this.draw_area.appendChild(slice); | |||
this.drawArea.appendChild(slice); | |||
this.slices.push(slice); | |||
this.slicesProperties.push({ | |||
@@ -2298,11 +2444,11 @@ class PieChart extends BaseChart { | |||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false); | |||
} | |||
bind_tooltip() { | |||
this.draw_area.addEventListener('mousemove',this.mouseMove); | |||
this.draw_area.addEventListener('mouseleave',this.mouseLeave); | |||
this.drawArea.addEventListener('mousemove',this.mouseMove); | |||
this.drawArea.addEventListener('mouseleave',this.mouseLeave); | |||
} | |||
show_summary() { | |||
renderLegend() { | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : this.labels; | |||
this.legend_totals.map((d, i) => { | |||
@@ -2408,7 +2554,7 @@ class Heatmap extends BaseChart { | |||
return valid; | |||
} | |||
setup_base_values() { | |||
setupConstants() { | |||
this.today = new Date(); | |||
if(!this.start) { | |||
@@ -2426,11 +2572,11 @@ class Heatmap extends BaseChart { | |||
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1; | |||
} | |||
set_width() { | |||
this.base_width = (this.no_of_cols + 3) * 12 ; | |||
setWidth() { | |||
this.baseWidth = (this.no_of_cols + 3) * 12 ; | |||
if(this.discrete_domains) { | |||
this.base_width += (12 * 12); | |||
this.baseWidth += (12 * 12); | |||
} | |||
} | |||
@@ -10,25 +10,88 @@ import BaseChart from './BaseChart'; | |||
export default class AxisChart extends BaseChart { | |||
constructor(args) { | |||
super(args); | |||
this.xAxisLabels = this.data.labels || []; | |||
this.y = this.data.datasets || []; | |||
this.is_series = args.is_series; | |||
this.format_tooltip_y = args.format_tooltip_y; | |||
this.format_tooltip_x = args.format_tooltip_x; | |||
this.zero_line = this.height; | |||
} | |||
validate_and_prepare_data() { | |||
validateAndPrepareData() { | |||
this.xAxisLabels = this.data.labels || []; | |||
this.y = this.data.datasets || []; | |||
this.y.forEach(function(d, i) { | |||
d.index = i; | |||
}, this); | |||
return true; | |||
} | |||
setupEmptyValues() { | |||
this.yAxisPositions = [this.height, this.height/2, 0]; | |||
this.yAxisLabels = ['0', '5', '10']; | |||
this.xPositions = [0, this.width/2, this.width]; | |||
this.xAxisLabels = ['0', '5', '10']; | |||
} | |||
setupComponents() { | |||
let self = this; | |||
this.yAxis = { | |||
layerClass: 'y axis', | |||
layer: undefined, | |||
make: self.makeYLines, | |||
makeArgs: [self.yAxisPositions, self.yAxisLabels, | |||
self.width, self.y_axis_mode], | |||
store: [], //this.yAxisLines | |||
animate: self.animateYLines | |||
}; | |||
this.xAxis = { | |||
layerClass: 'x axis', | |||
layer: undefined, | |||
make: self.makeXLines, | |||
makeArgs: [self.xPositions, self.xAxisLabels], | |||
store: [], //this.xAxisLines | |||
animate: self.animateXLines | |||
}; | |||
this.yMarkerLines = { | |||
// layerClass: 'y marker axis', | |||
// layer: undefined, | |||
// make: makeYMarkerLines, | |||
// makeArgs: [this.yMarkerPositions, this.yMarker], | |||
// store: [], | |||
// animate: animateYMarkerLines | |||
}; | |||
this.xMarkerLines = { | |||
// layerClass: 'x marker axis', | |||
// layer: undefined, | |||
// make: makeXMarkerLines, | |||
// makeArgs: [this.yMarkerPositions, this.xMarker], | |||
// store: [], | |||
// animate: animateXMarkerLines | |||
}; | |||
// Marker Regions | |||
// Indexed according to dataset | |||
this.dataUnits = { | |||
layerClass: 'y marker axis', | |||
layer: undefined, | |||
// make: makeXLines, | |||
// makeArgs: [this.xPositions, this.xAxisLabels], | |||
// store: [], | |||
// animate: animateXLines, | |||
indexed: 1 | |||
}; | |||
this.components = [ | |||
this.yAxis, | |||
// this.xAxis, | |||
// this.yMarkerLines, | |||
// this.xMarkerLines, | |||
// this.dataUnits, | |||
]; | |||
} | |||
setup_values() { | |||
this.data.datasets.map(d => { | |||
d.values = d.values.map(val => (!isNaN(val) ? val : 0)); | |||
@@ -39,7 +102,6 @@ export default class AxisChart extends BaseChart { | |||
setup_x() { | |||
this.set_avg_unit_width_and_x_offset(); | |||
if(this.xPositions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
} | |||
@@ -105,39 +167,39 @@ export default class AxisChart extends BaseChart { | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier); | |||
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
} | |||
setupLayers() { | |||
super.setupLayers(); | |||
// setupLayers() { | |||
// super.setupLayers(); | |||
// For markers | |||
this.y_axis_group = this.makeLayer('y axis'); | |||
this.x_axis_group = this.makeLayer('x axis'); | |||
this.specific_y_group = this.makeLayer('specific axis'); | |||
// // For markers | |||
// this.y_axis_group = this.makeLayer('y axis'); | |||
// this.x_axis_group = this.makeLayer('x axis'); | |||
// this.specific_y_group = this.makeLayer('specific axis'); | |||
// For Aggregation | |||
// this.sumGroup = this.makeLayer('data-points'); | |||
// this.averageGroup = this.makeLayer('chart-area'); | |||
// // For Aggregation | |||
// // this.sumGroup = this.makeLayer('data-points'); | |||
// // this.averageGroup = this.makeLayer('chart-area'); | |||
this.setupPreUnitLayers && this.setupPreUnitLayers(); | |||
// this.setupPreUnitLayers && this.setupPreUnitLayers(); | |||
// For Graph points | |||
this.svg_units_groups = []; | |||
this.y.map((d, i) => { | |||
this.svg_units_groups[i] = this.makeLayer( | |||
'data-points data-points-' + i); | |||
}); | |||
} | |||
// // For Graph points | |||
// this.svg_units_groups = []; | |||
// this.y.map((d, i) => { | |||
// this.svg_units_groups[i] = this.makeLayer( | |||
// 'data-points data-points-' + i); | |||
// }); | |||
// } | |||
renderComponents(init) { | |||
this.makeYLines(this.yAxisPositions, this.yAxisLabels); | |||
this.makeXLines(this.xPositions, this.xAxisLabels); | |||
this.draw_graph(init); | |||
// this.make_y_specifics(this.yAnnotationPositions, this.specific_values); | |||
} | |||
// renderComponents(init) { | |||
// this.makeYLines(this.yAxisPositions, this.yAxisLabels); | |||
// this.makeXLines(this.xPositions, this.xAxisLabels); | |||
// this.draw_graph(init); | |||
// // this.make_y_specifics(this.yAnnotationPositions, this.specific_values); | |||
// } | |||
makeXLines(positions, values) { | |||
let [start_at, height, text_start_at, | |||
@@ -180,14 +242,36 @@ export default class AxisChart extends BaseChart { | |||
}); | |||
} | |||
makeYLines(positions, values) { | |||
// makeYLines(positions, values) { | |||
// let [width, text_end_at, axis_line_class, | |||
// start_at] = getYLineProps(this.width, this.y_axis_mode); | |||
// this.yAxisLines = []; | |||
// this.y_axis_group.textContent = ''; | |||
// values.map((value, i) => { | |||
// let yLine = makeYLine( | |||
// start_at, | |||
// width, | |||
// text_end_at, | |||
// value, | |||
// 'y-value-text', | |||
// axis_line_class, | |||
// positions[i], | |||
// (value === 0 && i !== 0) // Non-first Zero line | |||
// ); | |||
// this.yAxisLines.push(yLine); | |||
// this.y_axis_group.appendChild(yLine); | |||
// }); | |||
// } | |||
makeYLines(positions, values, totalWidth, mode) { | |||
let [width, text_end_at, axis_line_class, | |||
start_at] = getYLineProps(this.width, this.y_axis_mode); | |||
start_at] = getYLineProps(totalWidth, mode); | |||
this.yAxisLines = []; | |||
this.y_axis_group.textContent = ''; | |||
values.map((value, i) => { | |||
let yLine = makeYLine( | |||
// this.yAxisLines = []; | |||
// this.y_axis_group.textContent = ''; | |||
return values.map((value, i) => { | |||
return makeYLine( | |||
start_at, | |||
width, | |||
text_end_at, | |||
@@ -197,8 +281,8 @@ export default class AxisChart extends BaseChart { | |||
positions[i], | |||
(value === 0 && i !== 0) // Non-first Zero line | |||
); | |||
this.yAxisLines.push(yLine); | |||
this.y_axis_group.appendChild(yLine); | |||
// this.yAxisLines.push(yLine); | |||
// this.y_axis_group.appendChild(yLine); | |||
}); | |||
} | |||
@@ -241,14 +325,14 @@ export default class AxisChart extends BaseChart { | |||
}, 350); | |||
} | |||
setup_navigation(init) { | |||
setupNavigation(init) { | |||
if(init) { | |||
// Hack: defer nav till initial updateData | |||
setTimeout(() => { | |||
super.setup_navigation(init); | |||
super.setupNavigation(init); | |||
}, 500); | |||
} else { | |||
super.setup_navigation(init); | |||
super.setupNavigation(init); | |||
} | |||
} | |||
@@ -33,7 +33,7 @@ export default class BarChart extends AxisChart { | |||
this.overlay = unit.cloneNode(); | |||
this.overlay.style.fill = '#000000'; | |||
this.overlay.style.opacity = '0.4'; | |||
this.draw_area.appendChild(this.overlay); | |||
this.drawArea.appendChild(this.overlay); | |||
} | |||
bind_overlay() { | |||
@@ -33,10 +33,8 @@ export default class BaseChart { | |||
title = '', | |||
subtitle = '', | |||
colors = [], | |||
summary = [], | |||
is_navigable = 0, | |||
has_legend = 0, | |||
type = '', | |||
@@ -50,46 +48,26 @@ export default class BaseChart { | |||
this.subtitle = subtitle; | |||
this.data = data; | |||
this.oldData = Object.assign({}, data); | |||
this.specific_values = data.specific_values || []; | |||
this.summary = summary; | |||
this.is_navigable = is_navigable; | |||
if(this.is_navigable) { | |||
this.current_index = 0; | |||
} | |||
this.has_legend = has_legend; | |||
this.setColors(colors, type); | |||
this.set_margins(height); | |||
this.setupConfiguration(arguments[0]); | |||
} | |||
get_different_chart(type) { | |||
if(type === this.type) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
if(!COMPATIBLE_CHARTS[this.type].includes(type)) { | |||
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type); | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'change_chart_type(type)' | |||
// that will update only the required elements, but for now ... | |||
return new Chart({ | |||
parent: this.raw_chart_args.parent, | |||
title: this.title, | |||
data: this.raw_chart_args.data, | |||
type: type, | |||
height: this.raw_chart_args.height, | |||
colors: use_color ? this.colors : undefined | |||
}); | |||
setupConfiguration(args) { | |||
// Make a this.config, that has stuff like showTooltip, | |||
// showLegend, which then all functions will check | |||
this.setColors(args.colors, args.type); | |||
this.set_margins(args.height); | |||
this.config = { | |||
showTooltip: 1, | |||
showLegend: 1, | |||
isNavigable: 0 | |||
}; | |||
} | |||
setColors(colors, type) { | |||
@@ -108,58 +86,81 @@ export default class BaseChart { | |||
} | |||
set_margins(height) { | |||
this.base_height = height; | |||
this.baseHeight = height; | |||
this.height = height - 40; | |||
this.translate_x = 60; | |||
this.translate_y = 10; | |||
} | |||
setup() { | |||
validate(){ | |||
if(!this.parent) { | |||
console.error("No parent element to render on was provided."); | |||
return; | |||
return false; | |||
} | |||
if(this.validate_and_prepare_data()) { | |||
this.bind_window_events(); | |||
this.refresh(true); | |||
if(!this.validateAndPrepareData()) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
validate_and_prepare_data() { | |||
validateAndPrepareData() { | |||
return true; | |||
} | |||
bind_window_events() { | |||
window.addEventListener('resize', () => this.refresh()); | |||
window.addEventListener('orientationchange', () => this.refresh()); | |||
setup() { | |||
if(this.validate()) { | |||
this._setup(); | |||
} | |||
} | |||
refresh(init=false) { | |||
// TODO: no init! | |||
this.setup_base_values(); | |||
this.set_width(); | |||
_setup() { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
this.setup_container(); | |||
this.setupLayers(); | |||
this.setupEmptyValues(); | |||
// this.setupComponents(); | |||
this.setup_values(); | |||
this.setup_utils(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.draw(true); | |||
} | |||
this.renderComponents(init); | |||
this.make_tooltip(); | |||
draw(init=false) { | |||
// (everything, layers, groups, units) | |||
this.setWidth(); | |||
if(this.summary.length > 0) { | |||
this.show_custom_summary(); | |||
} else { | |||
this.show_summary(); | |||
} | |||
// dependent on width >.<, how can this be decoupled | |||
this.setupComponents(); | |||
if(this.is_navigable) { | |||
this.setup_navigation(init); | |||
} | |||
this.makeChartArea(); | |||
this.makeLayers(); | |||
this.renderComponents(); // with zero values | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
if(init) this.update(this.data); | |||
} | |||
update(data, animate=true) { | |||
this.oldData = Object.assign({}, this.data); | |||
this.data = this.prepareNewData(data); | |||
this.calculateValues(); | |||
this.updateComponents(animate); | |||
} | |||
prepareNewData(newData) { | |||
// handle all types of passed data? | |||
return newData; | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize', () => this.draw()); | |||
window.addEventListener('orientationchange', () => this.draw()); | |||
} | |||
set_width() { | |||
setWidth() { | |||
let special_values_width = 0; | |||
let char_width = 8; | |||
this.specific_values.map(val => { | |||
@@ -172,9 +173,16 @@ export default class BaseChart { | |||
this.width = this.base_width - this.translate_x * 2; | |||
} | |||
setup_base_values() {} | |||
setupConstants() {} | |||
setup_container() { | |||
setupEmptyValues() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
makeContainer() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
@@ -189,35 +197,48 @@ export default class BaseChart { | |||
this.chart_wrapper = this.container.querySelector('.frappe-chart'); | |||
this.stats_wrapper = this.container.querySelector('.graph-stats-container'); | |||
this.make_chart_area(); | |||
this.make_draw_area(); | |||
} | |||
make_chart_area() { | |||
makeChartArea() { | |||
this.svg = makeSVGContainer( | |||
this.chart_wrapper, | |||
'chart', | |||
this.base_width, | |||
this.base_height | |||
this.baseWidth, | |||
this.baseHeight | |||
); | |||
this.svg_defs = makeSVGDefs(this.svg); | |||
return this.svg; | |||
} | |||
make_draw_area() { | |||
this.draw_area = makeSVGGroup( | |||
this.drawArea = makeSVGGroup( | |||
this.svg, | |||
this.type + '-chart', | |||
`translate(${this.translate_x}, ${this.translate_y})` | |||
); | |||
} | |||
setupLayers() {} | |||
setup_values() {} | |||
setup_utils() {} | |||
make_tooltip() { | |||
makeLayers() { | |||
this.components.forEach((component) => { | |||
component.layer = this.makeLayer(component.layerClass); | |||
}); | |||
} | |||
calculateValues() {} | |||
renderComponents() { | |||
this.components.forEach(c => { | |||
c.store = c.make(...c.makeArgs); | |||
c.layer.textContent = ''; | |||
c.store.forEach(element => {c.layer.appendChild(element);}); | |||
}); | |||
} | |||
updateComponents() { | |||
// this.components.forEach((component) => { | |||
// // | |||
// }); | |||
} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chart_wrapper, | |||
colors: this.colors | |||
@@ -238,8 +259,11 @@ export default class BaseChart { | |||
this.stats_wrapper.appendChild(stats); | |||
}); | |||
} | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
if(this.is_navigable) return; | |||
setup_navigation(init=false) { | |||
this.make_overlay(); | |||
if(init) { | |||
@@ -275,12 +299,37 @@ export default class BaseChart { | |||
on_down_arrow() {} | |||
on_enter_key() {} | |||
updateData() {} | |||
getDataPoint() {} | |||
updateCurrentDataPoint() {} | |||
makeLayer(className, transform='') { | |||
return makeSVGGroup(this.draw_area, className, transform); | |||
return makeSVGGroup(this.drawArea, className, transform); | |||
} | |||
get_different_chart(type) { | |||
if(type === this.type) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
if(!COMPATIBLE_CHARTS[this.type].includes(type)) { | |||
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type); | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'change_chart_type(type)' | |||
// that will update only the required elements, but for now ... | |||
return new Chart({ | |||
parent: this.raw_chart_args.parent, | |||
title: this.title, | |||
data: this.raw_chart_args.data, | |||
type: type, | |||
height: this.raw_chart_args.height, | |||
colors: use_color ? this.colors : undefined | |||
}); | |||
} | |||
} |
@@ -54,7 +54,7 @@ export default class Heatmap extends BaseChart { | |||
return valid; | |||
} | |||
setup_base_values() { | |||
setupConstants() { | |||
this.today = new Date(); | |||
if(!this.start) { | |||
@@ -72,11 +72,11 @@ export default class Heatmap extends BaseChart { | |||
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1; | |||
} | |||
set_width() { | |||
this.base_width = (this.no_of_cols + 3) * 12 ; | |||
setWidth() { | |||
this.baseWidth = (this.no_of_cols + 3) * 12 ; | |||
if(this.discrete_domains) { | |||
this.base_width += (12 * 12); | |||
this.baseWidth += (12 * 12); | |||
} | |||
} | |||
@@ -30,7 +30,7 @@ export default class LineChart extends AxisChart { | |||
this.paths_groups = []; | |||
this.y.map((d, i) => { | |||
this.paths_groups[i] = makeSVGGroup( | |||
this.draw_area, | |||
this.drawArea, | |||
'path-group path-group-' + i | |||
); | |||
}); | |||
@@ -12,29 +12,27 @@ export default class PercentageChart extends BaseChart { | |||
this.setup(); | |||
} | |||
make_chart_area() { | |||
makeChartArea() { | |||
this.chart_wrapper.className += ' ' + 'graph-focus-margin'; | |||
this.chart_wrapper.style.marginTop = '45px'; | |||
this.stats_wrapper.className += ' ' + 'graph-focus-margin'; | |||
this.stats_wrapper.style.marginBottom = '30px'; | |||
this.stats_wrapper.style.paddingTop = '0px'; | |||
} | |||
make_draw_area() { | |||
this.chart_div = $.create('div', { | |||
this.chartDiv = $.create('div', { | |||
className: 'div', | |||
inside: this.chart_wrapper | |||
}); | |||
this.chart = $.create('div', { | |||
className: 'progress-chart', | |||
inside: this.chart_div | |||
inside: this.chartDiv | |||
}); | |||
} | |||
setupLayers() { | |||
this.percentage_bar = $.create('div', { | |||
this.percentageBar = $.create('div', { | |||
className: 'progress', | |||
inside: this.chart | |||
}); | |||
@@ -81,7 +79,7 @@ export default class PercentageChart extends BaseChart { | |||
this.slice_totals.map((total, i) => { | |||
let slice = $.create('div', { | |||
className: `progress-bar`, | |||
inside: this.percentage_bar, | |||
inside: this.percentageBar, | |||
styles: { | |||
background: this.colors[i], | |||
width: total*100/this.grand_total + "%" | |||
@@ -108,7 +106,7 @@ export default class PercentageChart extends BaseChart { | |||
}); | |||
} | |||
show_summary() { | |||
renderLegend() { | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : this.labels; | |||
this.legend_totals.map((d, i) => { | |||
@@ -96,7 +96,7 @@ export default class PieChart extends BaseChart { | |||
const curPath = this.makeArcPath(curStart,curEnd); | |||
let slice = makePath(curPath, 'pie-path', 'none', this.colors[i]); | |||
slice.style.transition = 'transform .3s;'; | |||
this.draw_area.appendChild(slice); | |||
this.drawArea.appendChild(slice); | |||
this.slices.push(slice); | |||
this.slicesProperties.push({ | |||
@@ -166,11 +166,11 @@ export default class PieChart extends BaseChart { | |||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false); | |||
} | |||
bind_tooltip() { | |||
this.draw_area.addEventListener('mousemove',this.mouseMove); | |||
this.draw_area.addEventListener('mouseleave',this.mouseLeave); | |||
this.drawArea.addEventListener('mousemove',this.mouseMove); | |||
this.drawArea.addEventListener('mouseleave',this.mouseLeave); | |||
} | |||
show_summary() { | |||
renderLegend() { | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : this.labels; | |||
this.legend_totals.map((d, i) => { | |||
@@ -56,28 +56,60 @@ export var Animator = (function() { | |||
return pathComponents; | |||
}, | |||
verticalLine: function(xLine, newX, oldX) { | |||
translate: function(obj, oldCoord, newCoord, duration) { | |||
return [ | |||
{unit: xLine, array: [0], index: 0}, | |||
{transform: `${ newX }, 0`}, | |||
MARKER_LINE_ANIM_DUR, | |||
{unit: obj, array: [0], index: 0}, | |||
{transform: newCoord.join(', ')}, | |||
duration, | |||
STD_EASING, | |||
"translate", | |||
{transform: `${ oldX }, 0`} | |||
{transform: oldCoord.join(', ')} | |||
]; | |||
}, | |||
verticalLine: function(xLine, newX, oldX) { | |||
return this.translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR); | |||
}, | |||
horizontalLine: function(yLine, newY, oldY) { | |||
return [ | |||
{unit: yLine, array: [0], index: 0}, | |||
{transform: `0, ${ newY }`}, | |||
MARKER_LINE_ANIM_DUR, | |||
STD_EASING, | |||
"translate", | |||
{transform: `0, ${ oldY }`} | |||
]; | |||
return this.translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR); | |||
} | |||
}; | |||
return Animator; | |||
})(); | |||
export function animate_path(animator, d, newX, newY) { | |||
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y)); | |||
return this.animator.path(d, newPointsList.join("L")); | |||
} | |||
export function animate_units(animator, d, newX, newY, type, noOfDatasets) { | |||
// let type = this.unit_args.type; | |||
return d.svg_units.map((unit, i) => { | |||
// if(newX[i] === undefined || newY[i] === undefined) return; | |||
return animator[type]( | |||
{unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data | |||
newX[i], | |||
newY[i], | |||
d.index, | |||
noOfDatasets | |||
// this.y.length | |||
); | |||
}); | |||
} | |||
// export function animateXLines(animator, lines, oldX, newX) { | |||
// // this.xAxisLines.map((xLine, i) => { | |||
// return lines.map((xLine, i) => { | |||
// return animator.verticalLine(xLine, newX[i], oldX[i]); | |||
// }); | |||
// } | |||
// export function animateYLines(animator, lines, oldY, newY) { | |||
// // this.yAxisLines.map((yLine, i) => { | |||
// lines.map((yLine, i) => { | |||
// return animator.horizontalLine(yLine, newY[i], oldY[i]); | |||
// }); | |||
// } |
@@ -0,0 +1,26 @@ | |||
// export default class ChartComponent { | |||
// constructor({ | |||
// parent = null, | |||
// colors = [] | |||
// }) { | |||
// this.parent = parent; | |||
// this.colors = colors; | |||
// this.title_name = ''; | |||
// this.title_value = ''; | |||
// this.list_values = []; | |||
// this.title_value_first = 0; | |||
// this.x = 0; | |||
// this.y = 0; | |||
// this.top = 0; | |||
// this.left = 0; | |||
// this.setup(); | |||
// } | |||
// setup() { | |||
// this.make_tooltip(); | |||
// } | |||
// } |