Преглед изворни кода

[layout-svg] grid measure system for charts

tags/1.2.0
Prateeksha Singh пре 7 година
родитељ
комит
ea872c10cb
21 измењених фајлова са 369 додато и 398 уклоњено
  1. +160
    -184
      dist/frappe-charts.esm.js
  2. +1
    -1
      dist/frappe-charts.min.cjs.js
  3. +1
    -1
      dist/frappe-charts.min.css
  4. +1
    -1
      dist/frappe-charts.min.esm.js
  5. +1
    -1
      dist/frappe-charts.min.iife.js
  6. +1
    -1
      dist/frappe-charts.min.iife.js.map
  7. +1
    -1
      docs/assets/js/frappe-charts.min.js
  8. +1
    -1
      docs/assets/js/frappe-charts.min.js.map
  9. +6
    -6
      docs/assets/js/index.js
  10. +6
    -9
      docs/assets/js/index.min.js
  11. +1
    -1
      docs/assets/js/index.min.js.map
  12. +13
    -15
      docs/index.html
  13. +0
    -1
      rollup.config.js
  14. +4
    -0
      src/css/charts.scss
  15. +25
    -22
      src/js/charts/AxisChart.js
  16. +91
    -123
      src/js/charts/BaseChart.js
  17. +11
    -11
      src/js/charts/Heatmap.js
  18. +4
    -4
      src/js/charts/MultiAxisChart.js
  19. +3
    -3
      src/js/objects/ChartComponents.js
  20. +33
    -8
      src/js/utils/constants.js
  21. +5
    -4
      src/js/utils/draw.js

+ 160
- 184
dist/frappe-charts.esm.js Прегледај датотеку

@@ -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 COMPATIBLE_CHARTS = {
bar: ['line', 'scatter', 'percentage', 'pie'],
line: ['scatter', 'bar', 'percentage', 'pie'],
pie: ['line', 'scatter', 'percentage', 'bar'],
percentage: ['bar', 'line', 'scatter', 'pie'],
heatmap: []
};
const BASE_MEASURES = {
margins: {
top: 10,
bottom: 10,
left: 20,
right: 20
},
paddings: {
top: 20,
bottom: 40,
left: 30,
right: 10
},

const DATA_COLOR_DIVISIONS = {
bar: 'datasets',
line: 'datasets',
pie: 'labels',
percentage: 'labels',
heatmap: HEATMAP_DISTRIBUTION_SIZE
baseHeight: 240,
titleHeight: 20,
legendHeight: 30,

titleFontSize: 12,
};

const BASE_CHART_TOP_MARGIN = 10;
const BASE_CHART_LEFT_MARGIN = 20;
const BASE_CHART_RIGHT_MARGIN = 20;
function getExtraHeight(m) {
let totalExtraHeight = m.margins.top + m.margins.bottom
+ m.paddings.top + m.paddings.bottom
+ m.titleHeight + m.legendHeight;
return totalExtraHeight;
}

const Y_AXIS_LEFT_MARGIN = 60;
const Y_AXIS_RIGHT_MARGIN = 40;
function getExtraWidth(m) {
let totalExtraWidth = m.margins.left + m.margins.right
+ m.paddings.left + m.paddings.right;

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() {
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 - 70;
this.topMargin = BASE_CHART_TOP_MARGIN;
this.height = height - getExtraHeight(this.measures);

// Horizontal margins
this.leftMargin = BASE_CHART_LEFT_MARGIN;
this.rightMargin = BASE_CHART_RIGHT_MARGIN;
// 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);
setMeasures(options) {
if(this.data.datasets.length <= 1) {
this.config.showLegend = 0;
this.measures.paddings.bottom = 30;
}
}

args.axisOptions = args.axisOptions || {};
args.tooltipOptions = args.tooltipOptions || {};
configure(options) {
super.configure(options);

this.config.xAxisMode = args.axisOptions.xAxisMode || 'span';
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span';
this.config.xIsSeries = args.axisOptions.xIsSeries || 0;
options.axisOptions = options.axisOptions || {};
options.tooltipOptions = options.tooltipOptions || {};

this.config.formatTooltipX = args.tooltipOptions.formatTooltipX;
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY;
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;

this.config.valuesOverPoints = args.valuesOverPoints;
}
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX;
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;

setMargins() {
super.setMargins();
this.leftMargin = Y_AXIS_LEFT_MARGIN;
this.rightMargin = Y_AXIS_RIGHT_MARGIN;
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,


+ 1
- 1
dist/frappe-charts.min.cjs.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 1
dist/frappe-charts.min.css Прегледај датотеку

@@ -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}

+ 1
- 1
dist/frappe-charts.min.esm.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 1
dist/frappe-charts.min.iife.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 1
dist/frappe-charts.min.iife.js.map
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 1
docs/assets/js/frappe-charts.min.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 1
docs/assets/js/frappe-charts.min.js.map
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 6
- 6
docs/assets/js/index.js Прегледај датотеку

@@ -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]
};


+ 6
- 9
docs/assets/js/index.min.js Прегледај датотеку

@@ -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]
};


+ 1
- 1
docs/assets/js/index.min.js.map
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 13
- 15
docs/index.html Прегледај датотеку

@@ -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>


+ 0
- 1
rollup.config.js Прегледај датотеку

@@ -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])


+ 4
- 0
src/css/charts.scss Прегледај датотеку

@@ -49,6 +49,10 @@
text-anchor: middle;
}
}
.legend-dataset-text {
fill: #6c7680;
font-weight: 600;
}
}

.graph-svg-tip {


+ 25
- 22
src/js/charts/AxisChart.js Прегледај датотеку

@@ -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);
setMeasures(options) {
if(this.data.datasets.length <= 1) {
this.config.showLegend = 0;
this.measures.paddings.bottom = 30;
}
}

args.axisOptions = args.axisOptions || {};
args.tooltipOptions = args.tooltipOptions || {};
configure(options) {
super.configure(options);

this.config.xAxisMode = args.axisOptions.xAxisMode || 'span';
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span';
this.config.xIsSeries = args.axisOptions.xIsSeries || 0;
options.axisOptions = options.axisOptions || {};
options.tooltipOptions = options.tooltipOptions || {};

this.config.formatTooltipX = args.tooltipOptions.formatTooltipX;
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY;
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;

this.config.valuesOverPoints = args.valuesOverPoints;
}
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX;
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;

setMargins() {
super.setMargins();
this.leftMargin = Y_AXIS_LEFT_MARGIN;
this.rightMargin = Y_AXIS_RIGHT_MARGIN;
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


+ 91
- 123
src/js/charts/BaseChart.js Прегледај датотеку

@@ -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() {
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 - 70;
this.topMargin = BASE_CHART_TOP_MARGIN;
this.height = height - getExtraHeight(this.measures);

// Horizontal margins
this.leftMargin = BASE_CHART_LEFT_MARGIN;
this.rightMargin = BASE_CHART_RIGHT_MARGIN;
// 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() {


+ 11
- 11
src/js/charts/Heatmap.js Прегледај датотеку

@@ -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));
}


+ 4
- 4
src/js/charts/MultiAxisChart.js Прегледај датотеку

@@ -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() { }


+ 3
- 3
src/js/objects/ChartComponents.js Прегледај датотеку

@@ -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
}
)
);


+ 33
- 8
src/js/utils/constants.js Прегледај датотеку

@@ -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
},

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 const Y_AXIS_LEFT_MARGIN = 60;
export const Y_AXIS_RIGHT_MARGIN = 40;
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;



+ 5
- 4
src/js/utils/draw.js Прегледај датотеку

@@ -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…
Откажи
Сачувај