[layout-svg] grid measure system for charts
This commit is contained in:
parent
57c1fcc3a3
commit
ea872c10cb
354
dist/frappe-charts.esm.js
vendored
354
dist/frappe-charts.esm.js
vendored
@ -84,30 +84,40 @@ function fire(target, type, properties) {
|
||||
|
||||
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/
|
||||
|
||||
const ALL_CHART_TYPES = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];
|
||||
const BASE_MEASURES = {
|
||||
margins: {
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20,
|
||||
right: 20
|
||||
},
|
||||
paddings: {
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
right: 10
|
||||
},
|
||||
|
||||
const COMPATIBLE_CHARTS = {
|
||||
bar: ['line', 'scatter', 'percentage', 'pie'],
|
||||
line: ['scatter', 'bar', 'percentage', 'pie'],
|
||||
pie: ['line', 'scatter', 'percentage', 'bar'],
|
||||
percentage: ['bar', 'line', 'scatter', 'pie'],
|
||||
heatmap: []
|
||||
baseHeight: 240,
|
||||
titleHeight: 20,
|
||||
legendHeight: 30,
|
||||
|
||||
titleFontSize: 12,
|
||||
};
|
||||
|
||||
const DATA_COLOR_DIVISIONS = {
|
||||
bar: 'datasets',
|
||||
line: 'datasets',
|
||||
pie: 'labels',
|
||||
percentage: 'labels',
|
||||
heatmap: HEATMAP_DISTRIBUTION_SIZE
|
||||
};
|
||||
function getExtraHeight(m) {
|
||||
let totalExtraHeight = m.margins.top + m.margins.bottom
|
||||
+ m.paddings.top + m.paddings.bottom
|
||||
+ m.titleHeight + m.legendHeight;
|
||||
return totalExtraHeight;
|
||||
}
|
||||
|
||||
const BASE_CHART_TOP_MARGIN = 10;
|
||||
const BASE_CHART_LEFT_MARGIN = 20;
|
||||
const BASE_CHART_RIGHT_MARGIN = 20;
|
||||
function getExtraWidth(m) {
|
||||
let totalExtraWidth = m.margins.left + m.margins.right
|
||||
+ m.paddings.left + m.paddings.right;
|
||||
|
||||
const Y_AXIS_LEFT_MARGIN = 60;
|
||||
const Y_AXIS_RIGHT_MARGIN = 40;
|
||||
return totalExtraWidth;
|
||||
}
|
||||
|
||||
const INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||
const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
@ -130,9 +140,6 @@ const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
|
||||
// More colors are difficult to parse visually
|
||||
const HEATMAP_DISTRIBUTION_SIZE = 5;
|
||||
|
||||
const HEATMAP_LEFT_MARGIN = 50;
|
||||
const HEATMAP_TOP_MARGIN = 25;
|
||||
|
||||
const HEATMAP_SQUARE_SIZE = 10;
|
||||
const HEATMAP_GUTTER_SIZE = 2;
|
||||
|
||||
@ -282,10 +289,6 @@ class SvgTip {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a number upto 2 decimal places.
|
||||
* @param {Number} d Any number
|
||||
*/
|
||||
function floatTwo(d) {
|
||||
return parseFloat(d.toFixed(2));
|
||||
}
|
||||
@ -489,12 +492,13 @@ function makeSVGDefs(svgContainer) {
|
||||
});
|
||||
}
|
||||
|
||||
function makeSVGGroup(parent, className, transform='') {
|
||||
return createSVG('g', {
|
||||
function makeSVGGroup(className, transform='', parent=undefined) {
|
||||
let args = {
|
||||
className: className,
|
||||
inside: parent,
|
||||
transform: transform
|
||||
});
|
||||
};
|
||||
if(parent) args.inside = parent;
|
||||
return createSVG('g', args);
|
||||
}
|
||||
|
||||
|
||||
@ -1327,7 +1331,6 @@ class BaseChart {
|
||||
this.rawChartArgs = options;
|
||||
|
||||
this.title = options.title || '';
|
||||
this.argHeight = options.height || 240;
|
||||
this.type = options.type || '';
|
||||
|
||||
this.realData = this.prepareData(options.data);
|
||||
@ -1337,10 +1340,18 @@ class BaseChart {
|
||||
|
||||
this.config = {
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: options.showLegend || 1,
|
||||
showLegend: 1, // calculate
|
||||
isNavigable: options.isNavigable || 0,
|
||||
animate: 1
|
||||
};
|
||||
|
||||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
|
||||
let m = this.measures;
|
||||
this.setMeasures(options);
|
||||
if(!this.title.length) { m.titleHeight = 0; }
|
||||
if(!this.config.showLegend) m.legendHeight = 0;
|
||||
this.argHeight = options.height || m.baseHeight;
|
||||
|
||||
this.state = {};
|
||||
this.options = {};
|
||||
|
||||
@ -1353,12 +1364,12 @@ class BaseChart {
|
||||
this.configure(options);
|
||||
}
|
||||
|
||||
configure() {
|
||||
this.setMargins();
|
||||
prepareData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.boundDrawFn);
|
||||
window.addEventListener('orientationchange', () => this.boundDrawFn);
|
||||
prepareFirstData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
validateColors(colors, type) {
|
||||
@ -1375,17 +1386,22 @@ class BaseChart {
|
||||
return validColors;
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - 70;
|
||||
this.topMargin = BASE_CHART_TOP_MARGIN;
|
||||
|
||||
// Horizontal margins
|
||||
this.leftMargin = BASE_CHART_LEFT_MARGIN;
|
||||
this.rightMargin = BASE_CHART_RIGHT_MARGIN;
|
||||
setMeasures() {
|
||||
// Override measures, including those for title and legend
|
||||
// set config for legend and title
|
||||
}
|
||||
|
||||
configure() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - getExtraHeight(this.measures);
|
||||
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.draw(true));
|
||||
window.addEventListener('orientationchange', () => this.draw(true));
|
||||
}
|
||||
|
||||
// Has to be called manually
|
||||
setup() {
|
||||
this.makeContainer();
|
||||
this.updateWidth();
|
||||
@ -1394,10 +1410,6 @@ class BaseChart {
|
||||
this.draw(false, true);
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
makeContainer() {
|
||||
// Chart needs a dedicated parent element
|
||||
this.parent.innerHTML = '';
|
||||
@ -1444,11 +1456,71 @@ class BaseChart {
|
||||
this.setupNavigation(init);
|
||||
}
|
||||
|
||||
calc() {} // builds state
|
||||
|
||||
updateWidth() {
|
||||
this.baseWidth = getElementContentWidth(this.parent);
|
||||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin);
|
||||
this.width = this.baseWidth - getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.container.removeChild(this.svg);
|
||||
}
|
||||
let m = this.measures;
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
if(this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
m.margins.left,
|
||||
m.margins.top,
|
||||
this.title,
|
||||
{
|
||||
fontSize: m.titleFontSize,
|
||||
fill: '#666666',
|
||||
dy: m.titleFontSize
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let top = m.margins.top + m.titleHeight + m.paddings.top;
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.type + '-chart chart-draw-area',
|
||||
`translate(${m.margins.left + m.paddings.left}, ${top})`
|
||||
);
|
||||
|
||||
if(this.config.showLegend) {
|
||||
top += this.height + m.paddings.bottom;
|
||||
this.legendArea = makeSVGGroup(
|
||||
'chart-legend',
|
||||
`translate(${m.margins.left + m.paddings.left}, ${top})`
|
||||
);
|
||||
}
|
||||
|
||||
if(this.title.length) { this.svg.appendChild(this.titleEL); }
|
||||
this.svg.appendChild(this.drawArea);
|
||||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); }
|
||||
|
||||
this.updateTipOffset(m.margins.left + m.paddings.left, m.margins.top + m.paddings.top + m.titleHeight);
|
||||
}
|
||||
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
setupComponents() { this.components = new Map(); }
|
||||
|
||||
update(data) {
|
||||
if(!data) {
|
||||
console.error('No data to update.');
|
||||
@ -1458,16 +1530,6 @@ class BaseChart {
|
||||
this.render();
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
prepareFirstData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
calc() {} // builds state
|
||||
|
||||
render(components=this.components, animate=true) {
|
||||
if(this.config.isNavigable) {
|
||||
// Remove all existing overlays
|
||||
@ -1498,68 +1560,6 @@ class BaseChart {
|
||||
}
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.container.removeChild(this.svg);
|
||||
}
|
||||
|
||||
let titleAreaHeight = 0;
|
||||
let legendAreaHeight = 0;
|
||||
if(this.title.length) {
|
||||
titleAreaHeight = 40;
|
||||
}
|
||||
if(this.config.showLegend) {
|
||||
legendAreaHeight = 30;
|
||||
}
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight + titleAreaHeight + legendAreaHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
// console.log(this.baseHeight, titleAreaHeight, legendAreaHeight);
|
||||
|
||||
if(this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
this.leftMargin - AXIS_TICK_LENGTH * 6,
|
||||
this.topMargin,
|
||||
this.title,
|
||||
{
|
||||
fontSize: 12,
|
||||
fill: '#666666'
|
||||
}
|
||||
);
|
||||
this.svg.appendChild(this.titleEL);
|
||||
}
|
||||
|
||||
let top = this.topMargin + titleAreaHeight;
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.svg,
|
||||
this.type + '-chart',
|
||||
`translate(${this.leftMargin}, ${top})`
|
||||
);
|
||||
|
||||
top = this.baseHeight - titleAreaHeight;
|
||||
this.legendArea = makeSVGGroup(
|
||||
this.svg,
|
||||
'chart-legend',
|
||||
`translate(${this.leftMargin}, ${top})`
|
||||
);
|
||||
|
||||
this.updateTipOffset(this.leftMargin, this.topMargin + titleAreaHeight);
|
||||
}
|
||||
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
setupNavigation(init=false) {
|
||||
@ -1606,39 +1606,13 @@ class BaseChart {
|
||||
|
||||
updateDataset() {}
|
||||
|
||||
getDifferentChart(type) {
|
||||
const currentType = this.type;
|
||||
let args = this.rawChartArgs;
|
||||
if(type === currentType) return;
|
||||
|
||||
if(!ALL_CHART_TYPES.includes(type)) {
|
||||
console.error(`'${type}' is not a valid chart type.`);
|
||||
}
|
||||
|
||||
if(!COMPATIBLE_CHARTS[currentType].includes(type)) {
|
||||
console.error(`'${currentType}' chart cannot be converted to a '${type}' chart.`);
|
||||
}
|
||||
|
||||
// whether the new chart can use the existing colors
|
||||
const useColor = DATA_COLOR_DIVISIONS[currentType] === DATA_COLOR_DIVISIONS[type];
|
||||
|
||||
// Okay, this is anticlimactic
|
||||
// this function will need to actually be 'changeChartType(type)'
|
||||
// that will update only the required elements, but for now ...
|
||||
|
||||
args.type = type;
|
||||
args.colors = useColor ? args.colors : undefined;
|
||||
|
||||
return new Chart(this.parent, args);
|
||||
}
|
||||
|
||||
boundDrawFn() {
|
||||
this.draw(true);
|
||||
}
|
||||
|
||||
unbindWindowEvents(){
|
||||
window.removeEventListener('resize', () => this.boundDrawFn);
|
||||
window.removeEventListener('orientationchange', () => this.boundDrawFn);
|
||||
window.removeEventListener('resize', () => this.boundDrawFn.bind(this));
|
||||
window.removeEventListener('orientationchange', () => this.boundDrawFn.bind(this));
|
||||
}
|
||||
|
||||
export() {
|
||||
@ -1834,7 +1808,7 @@ class ChartComponent {
|
||||
}
|
||||
|
||||
setup(parent) {
|
||||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform);
|
||||
this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent);
|
||||
}
|
||||
|
||||
make() {
|
||||
@ -2039,9 +2013,9 @@ let componentConfigs = {
|
||||
data.cols.map((week, weekNo) => {
|
||||
if(weekNo === 1) {
|
||||
this.labels.push(
|
||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true),
|
||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(),
|
||||
{
|
||||
fontSize: 11
|
||||
fontSize: 9
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -2690,26 +2664,26 @@ class Heatmap extends BaseChart {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(options) {
|
||||
setMeasures(options) {
|
||||
let m = this.measures;
|
||||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
||||
super.configure(options);
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = HEATMAP_LEFT_MARGIN;
|
||||
this.topMargin = HEATMAP_TOP_MARGIN;
|
||||
m.paddings.top = ROW_HEIGHT * 3;
|
||||
m.paddings.bottom = 0;
|
||||
m.legendHeight = ROW_HEIGHT * 2;
|
||||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK
|
||||
+ getExtraHeight(m);
|
||||
|
||||
let d = this.data;
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
this.independentWidth = (getWeeksBetween(d.start, d.end)
|
||||
+ spacing) * COL_WIDTH + this.rightMargin + this.leftMargin;
|
||||
+ spacing) * COL_WIDTH + m.margins.right + m.margins.left;
|
||||
}
|
||||
|
||||
updateWidth() {
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
this.baseWidth = (this.state.noOfWeeks + spacing) * COL_WIDTH
|
||||
+ this.rightMargin + this.leftMargin;
|
||||
+ getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
@ -2910,7 +2884,7 @@ class Heatmap extends BaseChart {
|
||||
addDays(startOfWeek, 1);
|
||||
}
|
||||
|
||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue) {
|
||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) {
|
||||
addDays(startOfWeek, 1);
|
||||
cols.push(this.getCol(startOfWeek, month, true));
|
||||
}
|
||||
@ -3091,26 +3065,27 @@ class AxisChart extends BaseChart {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
|
||||
args.axisOptions = args.axisOptions || {};
|
||||
args.tooltipOptions = args.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0;
|
||||
|
||||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = args.valuesOverPoints;
|
||||
setMeasures(options) {
|
||||
if(this.data.datasets.length <= 1) {
|
||||
this.config.showLegend = 0;
|
||||
this.measures.paddings.bottom = 30;
|
||||
}
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = Y_AXIS_LEFT_MARGIN;
|
||||
this.rightMargin = Y_AXIS_RIGHT_MARGIN;
|
||||
configure(options) {
|
||||
super.configure(options);
|
||||
|
||||
options.axisOptions = options.axisOptions || {};
|
||||
options.tooltipOptions = options.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;
|
||||
|
||||
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = options.valuesOverPoints;
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
@ -3434,11 +3409,13 @@ class AxisChart extends BaseChart {
|
||||
bindTooltip() {
|
||||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
let m = this.measures;
|
||||
let o = getOffset(this.container);
|
||||
let relX = e.pageX - o.left - this.leftMargin;
|
||||
let relY = e.pageY - o.top - this.topMargin;
|
||||
let relX = e.pageX - o.left - m.margins.left - m.paddings.left;
|
||||
let relY = e.pageY - o.top;
|
||||
|
||||
if(relY < this.height + this.topMargin * 2) {
|
||||
if(relY < this.height + m.titleHeight + m.margins.top + m.paddings.top
|
||||
&& relY > m.titleHeight + m.margins.top + m.paddings.top) {
|
||||
this.mapTooltipXPosition(relX);
|
||||
} else {
|
||||
this.tip.hideTip();
|
||||
@ -3452,6 +3429,7 @@ class AxisChart extends BaseChart {
|
||||
|
||||
let index = getClosestInArray(relX, s.xAxis.positions, true);
|
||||
|
||||
console.log(relX, s.xAxis.positions[index], s.xAxis.positions, this.tip.offset.x);
|
||||
this.tip.setValues(
|
||||
s.xAxis.positions[index] + this.tip.offset.x,
|
||||
s.yExtremes[index] + this.tip.offset.y,
|
||||
@ -3471,12 +3449,11 @@ class AxisChart extends BaseChart {
|
||||
|
||||
renderLegend() {
|
||||
let s = this.data;
|
||||
this.legendArea.textContent = '';
|
||||
|
||||
if(s.datasets.length > 1) {
|
||||
this.legendArea.textContent = '';
|
||||
s.datasets.map((d, i) => {
|
||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
||||
// let rightEndPoint = this.baseWidth - this.leftMargin - this.rightMargin;
|
||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
||||
// let multiplier = s.datasets.length - i;
|
||||
let rect = legendBar(
|
||||
// rightEndPoint - multiplier * barWidth, // To right align
|
||||
@ -3638,7 +3615,6 @@ class AxisChart extends BaseChart {
|
||||
// removeDataPoint(index = 0) {}
|
||||
}
|
||||
|
||||
// import MultiAxisChart from './charts/MultiAxisChart';
|
||||
const chartTypes = {
|
||||
bar: AxisChart,
|
||||
line: AxisChart,
|
||||
|
||||
2
dist/frappe-charts.min.cjs.js
vendored
2
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.css
vendored
2
dist/frappe-charts.min.css
vendored
@ -1 +1 @@
|
||||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}
|
||||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}
|
||||
2
dist/frappe-charts.min.esm.js
vendored
2
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js.map
vendored
2
dist/frappe-charts.min.iife.js.map
vendored
File diff suppressed because one or more lines are too long
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -29,7 +29,7 @@ let lineCompositeChart = new Chart (c1, {
|
||||
let barCompositeChart = new Chart (c2, {
|
||||
data: barCompositeData,
|
||||
type: 'bar',
|
||||
height: 190,
|
||||
height: 210,
|
||||
colors: ['violet', 'light-blue', '#46a9f9'],
|
||||
valuesOverPoints: 1,
|
||||
axisOptions: {
|
||||
@ -55,7 +55,7 @@ let typeChartArgs = {
|
||||
title: "My Awesome Chart",
|
||||
data: typeData,
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: customColors,
|
||||
|
||||
maxLegendPoints: 6,
|
||||
@ -139,7 +139,7 @@ let updateData = {
|
||||
let updateChart = new Chart("#chart-update", {
|
||||
data: updateData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#ff6c03'],
|
||||
lineOptions: {
|
||||
// hideLine: 1,
|
||||
@ -198,7 +198,7 @@ let plotChartArgs = {
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trendsData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#238e38'],
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
@ -263,7 +263,7 @@ let eventsChart = new Chart("#chart-events", {
|
||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)",
|
||||
data: eventsData,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
height: 330,
|
||||
colors: ['grey'],
|
||||
isNavigable: 1,
|
||||
});
|
||||
@ -286,8 +286,8 @@ let heatmapArgs = {
|
||||
title: "Monthly Distribution",
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
discreteDomains: 1,
|
||||
countLabel: 'Level',
|
||||
colors: HEATMAP_COLORS_BLUE,
|
||||
legendScale: [0, 1, 2, 4, 5]
|
||||
};
|
||||
|
||||
15
docs/assets/js/index.min.js
vendored
15
docs/assets/js/index.min.js
vendored
@ -39,9 +39,6 @@ function __$styleInject(css, ref) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
||||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
|
||||
@ -319,7 +316,7 @@ var lineCompositeChart = new Chart(c1, {
|
||||
var barCompositeChart = new Chart(c2, {
|
||||
data: barCompositeData,
|
||||
type: 'bar',
|
||||
height: 190,
|
||||
height: 210,
|
||||
colors: ['violet', 'light-blue', '#46a9f9'],
|
||||
valuesOverPoints: 1,
|
||||
axisOptions: {
|
||||
@ -343,7 +340,7 @@ var typeChartArgs = {
|
||||
title: "My Awesome Chart",
|
||||
data: typeData,
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: customColors,
|
||||
|
||||
maxLegendPoints: 6,
|
||||
@ -430,7 +427,7 @@ var updateData = {
|
||||
var updateChart = new Chart("#chart-update", {
|
||||
data: updateData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#ff6c03'],
|
||||
lineOptions: {
|
||||
// hideLine: 1,
|
||||
@ -483,7 +480,7 @@ var plotChartArgs = {
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trendsData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#238e38'],
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
@ -543,7 +540,7 @@ var eventsChart = new Chart("#chart-events", {
|
||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)",
|
||||
data: eventsData,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
height: 330,
|
||||
colors: ['grey'],
|
||||
isNavigable: 1
|
||||
});
|
||||
@ -566,8 +563,8 @@ var heatmapArgs = {
|
||||
title: "Monthly Distribution",
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
discreteDomains: 1,
|
||||
countLabel: 'Level',
|
||||
colors: HEATMAP_COLORS_BLUE,
|
||||
legendScale: [0, 1, 2, 4, 5]
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -27,7 +27,7 @@
|
||||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
|
||||
<div class="jumbotron" style="background: transparent;">
|
||||
<h1>Frappe Charts</h1>
|
||||
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p>
|
||||
<p class="mt-2">GitHub-inspired simple and modern SVG charts for the web</p>
|
||||
<p class="mt-2">with zero dependencies.</p>
|
||||
</div>
|
||||
|
||||
@ -75,7 +75,7 @@
|
||||
|
||||
title: "My Awesome Chart",
|
||||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage'
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['purple', '#ffa3ef', 'red']
|
||||
});
|
||||
|
||||
@ -206,21 +206,19 @@
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", {
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
data: heatmapData, // object with date/timestamp-value pairs
|
||||
|
||||
discreteDomains: 1 // default: 0
|
||||
|
||||
start: startDate,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
title: "Monthly Distribution",
|
||||
data: {
|
||||
dataPoints: {'1524064033': 8, /* ... */},
|
||||
// object with timestamp-value pairs
|
||||
start: startDate
|
||||
end: endDate // Date objects
|
||||
},
|
||||
countLabel: 'Level',
|
||||
discreteDomains: 0 // default: 1
|
||||
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'],
|
||||
// Set of five incremental colors,
|
||||
// beginning with a low-saturation color for zero data;
|
||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
|
||||
// preferably with a low-saturation color for zero data;
|
||||
// def: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,6 @@ import precss from 'precss';
|
||||
import CleanCSS from 'clean-css';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import fs from 'fs';
|
||||
import { HEATMAP_LEFT_MARGIN } from './src/js/utils/constants';
|
||||
|
||||
fs.readFile('src/css/charts.scss', (err, css) => {
|
||||
postcss([precss, autoprefixer])
|
||||
|
||||
@ -49,6 +49,10 @@
|
||||
text-anchor: middle;
|
||||
}
|
||||
}
|
||||
.legend-dataset-text {
|
||||
fill: #6c7680;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.graph-svg-tip {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
||||
import { Y_AXIS_LEFT_MARGIN, Y_AXIS_RIGHT_MARGIN, AXIS_LEGEND_BAR_SIZE } from '../utils/constants';
|
||||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { getOffset, fire } from '../utils/dom';
|
||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
||||
@ -21,26 +21,27 @@ export default class AxisChart extends BaseChart {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
|
||||
args.axisOptions = args.axisOptions || {};
|
||||
args.tooltipOptions = args.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0;
|
||||
|
||||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = args.valuesOverPoints;
|
||||
setMeasures(options) {
|
||||
if(this.data.datasets.length <= 1) {
|
||||
this.config.showLegend = 0;
|
||||
this.measures.paddings.bottom = 30;
|
||||
}
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = Y_AXIS_LEFT_MARGIN;
|
||||
this.rightMargin = Y_AXIS_RIGHT_MARGIN;
|
||||
configure(options) {
|
||||
super.configure(options);
|
||||
|
||||
options.axisOptions = options.axisOptions || {};
|
||||
options.tooltipOptions = options.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;
|
||||
|
||||
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = options.valuesOverPoints;
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
@ -364,11 +365,13 @@ export default class AxisChart extends BaseChart {
|
||||
bindTooltip() {
|
||||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
let m = this.measures;
|
||||
let o = getOffset(this.container);
|
||||
let relX = e.pageX - o.left - this.leftMargin;
|
||||
let relY = e.pageY - o.top - this.topMargin;
|
||||
let relX = e.pageX - o.left - m.margins.left - m.paddings.left;
|
||||
let relY = e.pageY - o.top;
|
||||
|
||||
if(relY < this.height + this.topMargin * 2) {
|
||||
if(relY < this.height + m.titleHeight + m.margins.top + m.paddings.top
|
||||
&& relY > m.titleHeight + m.margins.top + m.paddings.top) {
|
||||
this.mapTooltipXPosition(relX);
|
||||
} else {
|
||||
this.tip.hideTip();
|
||||
@ -382,6 +385,7 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
let index = getClosestInArray(relX, s.xAxis.positions, true);
|
||||
|
||||
console.log(relX, s.xAxis.positions[index], s.xAxis.positions, this.tip.offset.x);
|
||||
this.tip.setValues(
|
||||
s.xAxis.positions[index] + this.tip.offset.x,
|
||||
s.yExtremes[index] + this.tip.offset.y,
|
||||
@ -401,12 +405,11 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
renderLegend() {
|
||||
let s = this.data;
|
||||
this.legendArea.textContent = '';
|
||||
|
||||
if(s.datasets.length > 1) {
|
||||
this.legendArea.textContent = '';
|
||||
s.datasets.map((d, i) => {
|
||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
||||
// let rightEndPoint = this.baseWidth - this.leftMargin - this.rightMargin;
|
||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
||||
// let multiplier = s.datasets.length - i;
|
||||
let rect = legendBar(
|
||||
// rightEndPoint - multiplier * barWidth, // To right align
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import SvgTip from '../objects/SvgTip';
|
||||
import { $, isElementInViewport, getElementContentWidth } from '../utils/dom';
|
||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText, AXIS_TICK_LENGTH } from '../utils/draw';
|
||||
import { BASE_CHART_TOP_MARGIN, BASE_CHART_LEFT_MARGIN,
|
||||
BASE_CHART_RIGHT_MARGIN, INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS,
|
||||
ALL_CHART_TYPES, COMPATIBLE_CHARTS, DATA_COLOR_DIVISIONS} from '../utils/constants';
|
||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText, yLine } from '../utils/draw';
|
||||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT,
|
||||
DEFAULT_COLORS} from '../utils/constants';
|
||||
import { getColor, isValidColor } from '../utils/colors';
|
||||
import { runSMILAnimation } from '../utils/animation';
|
||||
import { downloadFile, prepareForExport } from '../utils/export';
|
||||
import { Chart } from '../chart';
|
||||
|
||||
export default class BaseChart {
|
||||
constructor(parent, options) {
|
||||
@ -23,7 +21,6 @@ export default class BaseChart {
|
||||
this.rawChartArgs = options;
|
||||
|
||||
this.title = options.title || '';
|
||||
this.argHeight = options.height || 240;
|
||||
this.type = options.type || '';
|
||||
|
||||
this.realData = this.prepareData(options.data);
|
||||
@ -33,10 +30,18 @@ export default class BaseChart {
|
||||
|
||||
this.config = {
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: options.showLegend || 1,
|
||||
showLegend: 1, // calculate
|
||||
isNavigable: options.isNavigable || 0,
|
||||
animate: 1
|
||||
};
|
||||
|
||||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
|
||||
let m = this.measures;
|
||||
this.setMeasures(options);
|
||||
if(!this.title.length) { m.titleHeight = 0; }
|
||||
if(!this.config.showLegend) m.legendHeight = 0;
|
||||
this.argHeight = options.height || m.baseHeight;
|
||||
|
||||
this.state = {};
|
||||
this.options = {};
|
||||
|
||||
@ -49,12 +54,12 @@ export default class BaseChart {
|
||||
this.configure(options);
|
||||
}
|
||||
|
||||
configure() {
|
||||
this.setMargins();
|
||||
prepareData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.boundDrawFn);
|
||||
window.addEventListener('orientationchange', () => this.boundDrawFn);
|
||||
prepareFirstData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
validateColors(colors, type) {
|
||||
@ -71,17 +76,22 @@ export default class BaseChart {
|
||||
return validColors;
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - 70;
|
||||
this.topMargin = BASE_CHART_TOP_MARGIN;
|
||||
|
||||
// Horizontal margins
|
||||
this.leftMargin = BASE_CHART_LEFT_MARGIN;
|
||||
this.rightMargin = BASE_CHART_RIGHT_MARGIN;
|
||||
setMeasures() {
|
||||
// Override measures, including those for title and legend
|
||||
// set config for legend and title
|
||||
}
|
||||
|
||||
configure() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - getExtraHeight(this.measures);
|
||||
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.draw(true));
|
||||
window.addEventListener('orientationchange', () => this.draw(true));
|
||||
}
|
||||
|
||||
// Has to be called manually
|
||||
setup() {
|
||||
this.makeContainer();
|
||||
this.updateWidth();
|
||||
@ -90,10 +100,6 @@ export default class BaseChart {
|
||||
this.draw(false, true);
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
makeContainer() {
|
||||
// Chart needs a dedicated parent element
|
||||
this.parent.innerHTML = '';
|
||||
@ -140,11 +146,71 @@ export default class BaseChart {
|
||||
this.setupNavigation(init);
|
||||
}
|
||||
|
||||
calc() {} // builds state
|
||||
|
||||
updateWidth() {
|
||||
this.baseWidth = getElementContentWidth(this.parent);
|
||||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin);
|
||||
this.width = this.baseWidth - getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.container.removeChild(this.svg);
|
||||
}
|
||||
let m = this.measures;
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
if(this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
m.margins.left,
|
||||
m.margins.top,
|
||||
this.title,
|
||||
{
|
||||
fontSize: m.titleFontSize,
|
||||
fill: '#666666',
|
||||
dy: m.titleFontSize
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let top = m.margins.top + m.titleHeight + m.paddings.top;
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.type + '-chart chart-draw-area',
|
||||
`translate(${m.margins.left + m.paddings.left}, ${top})`
|
||||
);
|
||||
|
||||
if(this.config.showLegend) {
|
||||
top += this.height + m.paddings.bottom;
|
||||
this.legendArea = makeSVGGroup(
|
||||
'chart-legend',
|
||||
`translate(${m.margins.left + m.paddings.left}, ${top})`
|
||||
);
|
||||
}
|
||||
|
||||
if(this.title.length) { this.svg.appendChild(this.titleEL); }
|
||||
this.svg.appendChild(this.drawArea);
|
||||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); }
|
||||
|
||||
this.updateTipOffset(m.margins.left + m.paddings.left, m.margins.top + m.paddings.top + m.titleHeight);
|
||||
}
|
||||
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
setupComponents() { this.components = new Map(); }
|
||||
|
||||
update(data) {
|
||||
if(!data) {
|
||||
console.error('No data to update.');
|
||||
@ -154,16 +220,6 @@ export default class BaseChart {
|
||||
this.render();
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
prepareFirstData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
calc() {} // builds state
|
||||
|
||||
render(components=this.components, animate=true) {
|
||||
if(this.config.isNavigable) {
|
||||
// Remove all existing overlays
|
||||
@ -194,68 +250,6 @@ export default class BaseChart {
|
||||
}
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.container.removeChild(this.svg);
|
||||
}
|
||||
|
||||
let titleAreaHeight = 0;
|
||||
let legendAreaHeight = 0;
|
||||
if(this.title.length) {
|
||||
titleAreaHeight = 40;
|
||||
}
|
||||
if(this.config.showLegend) {
|
||||
legendAreaHeight = 30;
|
||||
}
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight + titleAreaHeight + legendAreaHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
// console.log(this.baseHeight, titleAreaHeight, legendAreaHeight);
|
||||
|
||||
if(this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
this.leftMargin - AXIS_TICK_LENGTH * 6,
|
||||
this.topMargin,
|
||||
this.title,
|
||||
{
|
||||
fontSize: 12,
|
||||
fill: '#666666'
|
||||
}
|
||||
);
|
||||
this.svg.appendChild(this.titleEL);
|
||||
}
|
||||
|
||||
let top = this.topMargin + titleAreaHeight;
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.svg,
|
||||
this.type + '-chart',
|
||||
`translate(${this.leftMargin}, ${top})`
|
||||
);
|
||||
|
||||
top = this.baseHeight - titleAreaHeight;
|
||||
this.legendArea = makeSVGGroup(
|
||||
this.svg,
|
||||
'chart-legend',
|
||||
`translate(${this.leftMargin}, ${top})`
|
||||
);
|
||||
|
||||
this.updateTipOffset(this.leftMargin, this.topMargin + titleAreaHeight);
|
||||
}
|
||||
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
setupNavigation(init=false) {
|
||||
@ -302,39 +296,13 @@ export default class BaseChart {
|
||||
|
||||
updateDataset() {}
|
||||
|
||||
getDifferentChart(type) {
|
||||
const currentType = this.type;
|
||||
let args = this.rawChartArgs;
|
||||
if(type === currentType) return;
|
||||
|
||||
if(!ALL_CHART_TYPES.includes(type)) {
|
||||
console.error(`'${type}' is not a valid chart type.`);
|
||||
}
|
||||
|
||||
if(!COMPATIBLE_CHARTS[currentType].includes(type)) {
|
||||
console.error(`'${currentType}' chart cannot be converted to a '${type}' chart.`);
|
||||
}
|
||||
|
||||
// whether the new chart can use the existing colors
|
||||
const useColor = DATA_COLOR_DIVISIONS[currentType] === DATA_COLOR_DIVISIONS[type];
|
||||
|
||||
// Okay, this is anticlimactic
|
||||
// this function will need to actually be 'changeChartType(type)'
|
||||
// that will update only the required elements, but for now ...
|
||||
|
||||
args.type = type;
|
||||
args.colors = useColor ? args.colors : undefined;
|
||||
|
||||
return new Chart(this.parent, args);
|
||||
}
|
||||
|
||||
boundDrawFn() {
|
||||
this.draw(true);
|
||||
}
|
||||
|
||||
unbindWindowEvents(){
|
||||
window.removeEventListener('resize', () => this.boundDrawFn);
|
||||
window.removeEventListener('orientationchange', () => this.boundDrawFn);
|
||||
window.removeEventListener('resize', () => this.boundDrawFn.bind(this));
|
||||
window.removeEventListener('orientationchange', () => this.boundDrawFn.bind(this));
|
||||
}
|
||||
|
||||
export() {
|
||||
|
||||
@ -4,7 +4,7 @@ import { makeText, heatSquare } from '../utils/draw';
|
||||
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone,
|
||||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils';
|
||||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
|
||||
import { HEATMAP_TOP_MARGIN, HEATMAP_LEFT_MARGIN, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
|
||||
import { getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
|
||||
HEATMAP_GUTTER_SIZE } from '../utils/constants';
|
||||
|
||||
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE;
|
||||
@ -26,26 +26,26 @@ export default class Heatmap extends BaseChart {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(options) {
|
||||
setMeasures(options) {
|
||||
let m = this.measures;
|
||||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
||||
super.configure(options);
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = HEATMAP_LEFT_MARGIN;
|
||||
this.topMargin = HEATMAP_TOP_MARGIN;
|
||||
m.paddings.top = ROW_HEIGHT * 3;
|
||||
m.paddings.bottom = 0;
|
||||
m.legendHeight = ROW_HEIGHT * 2;
|
||||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK
|
||||
+ getExtraHeight(m);
|
||||
|
||||
let d = this.data;
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
this.independentWidth = (getWeeksBetween(d.start, d.end)
|
||||
+ spacing) * COL_WIDTH + this.rightMargin + this.leftMargin;
|
||||
+ spacing) * COL_WIDTH + m.margins.right + m.margins.left;
|
||||
}
|
||||
|
||||
updateWidth() {
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
this.baseWidth = (this.state.noOfWeeks + spacing) * COL_WIDTH
|
||||
+ this.rightMargin + this.leftMargin;
|
||||
+ getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
@ -246,7 +246,7 @@ export default class Heatmap extends BaseChart {
|
||||
addDays(startOfWeek, 1);
|
||||
}
|
||||
|
||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue) {
|
||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) {
|
||||
addDays(startOfWeek, 1);
|
||||
cols.push(this.getCol(startOfWeek, month, true));
|
||||
}
|
||||
|
||||
@ -14,11 +14,11 @@ export default class MultiAxisChart extends AxisChart {
|
||||
this.type = 'multiaxis';
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
setMeasures() {
|
||||
super.setMeasures();
|
||||
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length;
|
||||
this.leftMargin = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
this.rightMargin = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
this.measures.margins.left = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
this.measures.margins.right = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
}
|
||||
|
||||
prepareYAxis() { }
|
||||
|
||||
@ -38,7 +38,7 @@ class ChartComponent {
|
||||
}
|
||||
|
||||
setup(parent) {
|
||||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform);
|
||||
this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent);
|
||||
}
|
||||
|
||||
make() {
|
||||
@ -243,9 +243,9 @@ let componentConfigs = {
|
||||
data.cols.map((week, weekNo) => {
|
||||
if(weekNo === 1) {
|
||||
this.labels.push(
|
||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true),
|
||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(),
|
||||
{
|
||||
fontSize: 11
|
||||
fontSize: 9
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@ -16,12 +16,40 @@ export const DATA_COLOR_DIVISIONS = {
|
||||
heatmap: HEATMAP_DISTRIBUTION_SIZE
|
||||
};
|
||||
|
||||
export const BASE_CHART_TOP_MARGIN = 10;
|
||||
export const BASE_CHART_LEFT_MARGIN = 20;
|
||||
export const BASE_CHART_RIGHT_MARGIN = 20;
|
||||
export const BASE_MEASURES = {
|
||||
margins: {
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20,
|
||||
right: 20
|
||||
},
|
||||
paddings: {
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
right: 10
|
||||
},
|
||||
|
||||
export const Y_AXIS_LEFT_MARGIN = 60;
|
||||
export const Y_AXIS_RIGHT_MARGIN = 40;
|
||||
baseHeight: 240,
|
||||
titleHeight: 20,
|
||||
legendHeight: 30,
|
||||
|
||||
titleFontSize: 12,
|
||||
};
|
||||
|
||||
export function getExtraHeight(m) {
|
||||
let totalExtraHeight = m.margins.top + m.margins.bottom
|
||||
+ m.paddings.top + m.paddings.bottom
|
||||
+ m.titleHeight + m.legendHeight;
|
||||
return totalExtraHeight;
|
||||
}
|
||||
|
||||
export function getExtraWidth(m) {
|
||||
let totalExtraWidth = m.margins.left + m.margins.right
|
||||
+ m.paddings.left + m.paddings.right;
|
||||
|
||||
return totalExtraWidth;
|
||||
}
|
||||
|
||||
export const INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||
export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
@ -44,9 +72,6 @@ export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
|
||||
// More colors are difficult to parse visually
|
||||
export const HEATMAP_DISTRIBUTION_SIZE = 5;
|
||||
|
||||
export const HEATMAP_LEFT_MARGIN = 50;
|
||||
export const HEATMAP_TOP_MARGIN = 25;
|
||||
|
||||
export const HEATMAP_SQUARE_SIZE = 10;
|
||||
export const HEATMAP_GUTTER_SIZE = 2;
|
||||
|
||||
|
||||
@ -81,12 +81,13 @@ export function makeSVGDefs(svgContainer) {
|
||||
});
|
||||
}
|
||||
|
||||
export function makeSVGGroup(parent, className, transform='') {
|
||||
return createSVG('g', {
|
||||
export function makeSVGGroup(className, transform='', parent=undefined) {
|
||||
let args = {
|
||||
className: className,
|
||||
inside: parent,
|
||||
transform: transform
|
||||
});
|
||||
};
|
||||
if(parent) args.inside = parent;
|
||||
return createSVG('g', args);
|
||||
}
|
||||
|
||||
export function wrapInSVGGroup(elements, className='') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user