瀏覽代碼

axis chart renderer

tags/1.2.0
pratu16x7 7 年之前
父節點
當前提交
02685dab7e
共有 14 個檔案被更改,包括 497 行新增527 行删除
  1. +232
    -246
      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
      docs/assets/js/frappe-charts.min.js
  7. +35
    -32
      src/js/charts/AxisChart.js
  8. +5
    -5
      src/js/charts/BarChart.js
  9. +61
    -52
      src/js/charts/BaseChart.js
  10. +1
    -1
      src/js/charts/LineChart.js
  11. +2
    -0
      src/js/renderers/AxisChartRenderer.js
  12. +21
    -60
      src/js/utils/draw-utils.js
  13. +128
    -119
      src/js/utils/draw.js
  14. +7
    -7
      src/scss/charts.scss

+ 232
- 246
dist/frappe-charts.esm.js 查看文件

@@ -247,62 +247,6 @@ function fillArray(array, count, element, start=false) {

const MIN_BAR_PERCENT_HEIGHT = 0.01;



function getXLineProps(totalHeight, mode) {
let startAt = totalHeight + 6, height, textStartAt, axisLineClass = '';
if(mode === 'span') { // long spanning lines
startAt = -7;
height = totalHeight + 15;
textStartAt = totalHeight + 25;
} else if(mode === 'tick'){ // short label lines
startAt = totalHeight;
height = 6;
textStartAt = 9;
axisLineClass = 'x-axis-label';
}

return [startAt, height, textStartAt, axisLineClass];
}

// export function getYLineProps(totalWidth, mode, specific=false) {
function getYLineProps(totalWidth, mode) {
// if(specific) {
// return[totalWidth, totalWidth + 5, 'specific-value', 0];
// }
let width, text_end_at = -9, axisLineClass = '', startAt = 0;
if(mode === 'span') { // long spanning lines
width = totalWidth + 6;
startAt = -6;
} else if(mode === 'tick'){ // short label lines
width = -6;
axisLineClass = 'y-axis-label';
}

return [width, text_end_at, axisLineClass, startAt];
}

// let char_width = 8;
// let allowed_space = avg_unit_width * 1.5;
// let allowed_letters = allowed_space / 8;

// return values.map((value, i) => {
// let space_taken = getStringWidth(value, char_width) + 2;
// if(space_taken > allowed_space) {
// if(is_series) {
// // Skip some axis lines if X axis is a series
// let skips = 1;
// while((space_taken/skips)*2 > allowed_space) {
// skips++;
// }
// if(i % skips !== 0) {
// return;
// }
// } else {
// value = value.slice(0, allowed_letters-3) + " ...";
// }
// }

function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
let height, y;
if (yTop <= zeroLine) {
@@ -338,11 +282,30 @@ function equilizeNoOfElements(array1, array2,
return [array1, array2];
}

const X_LABEL_CLASS = 'x-value-text';
const Y_LABEL_CLASS = 'y-value-text';
// let char_width = 8;
// let allowed_space = avgUnitWidth * 1.5;
// let allowed_letters = allowed_space / 8;

// return values.map((value, i) => {
// let space_taken = getStringWidth(value, char_width) + 2;
// if(space_taken > allowed_space) {
// if(is_series) {
// // Skip some axis lines if X axis is a series
// let skips = 1;
// while((space_taken/skips)*2 > allowed_space) {
// skips++;
// }
// if(i % skips !== 0) {
// return;
// }
// } else {
// value = value.slice(0, allowed_letters-3) + " ...";
// }
// }

// const X_AXIS_LINE_CLASS = 'x-value-text';
// const Y_AXIS_LINE_CLASS = 'y-value-text';
const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4;
const FONT_SIZE = 10;

function $$1(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
@@ -472,125 +435,136 @@ function makeText(className, x, y, content) {
className: className,
x: x,
y: y,
dy: '.32em',
dy: (FONT_SIZE / 2) + 'px',
'font-size': FONT_SIZE + 'px',
innerHTML: content
});
}

var AxisChartRenderer = (function() {
var AxisChartRenderer = function(totalHeight, totalWidth, zeroLine, avgUnitWidth, xAxisMode, yAxisMode) {
this.totalHeight = totalHeight;
this.totalWidth = totalWidth;
this.zeroLine = zeroLine;
this.avgUnitWidth = avgUnitWidth;
this.xAxisMode = xAxisMode;
this.yAxisMode = yAxisMode;
};
function makeVertXLine(x, label, totalHeight, mode) {
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight;

AxisChartRenderer.prototype = {
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.avgUnitWidth - args.spaceWidth;
let startX = x - totalWidth/2;
let l = createSVG('line', {
x1: 0,
x2: 0,
y1: totalHeight + AXIS_TICK_LENGTH,
y2: height
});

let width = totalWidth / noOfDatasets;
let currentX = startX + width * datasetIndex;
let text = createSVG('text', {
x: 0,
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN,
dy: FONT_SIZE + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle',
innerHTML: label
});

let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);
let line = createSVG('g', {
transform: `translate(${ x }, 0)`
});

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
},
line.appendChild(l);
line.appendChild(text);

dot: function(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
},
return line;
}

xLine: function(x, label, mode=this.xAxisMode) {
// Draw X axis line in span/tick mode with optional label
let [startAt, height, textStartAt, axisLineClass] = getXLineProps(this.totalHeight, mode);
let l = createSVG('line', {
x1: 0,
x2: 0,
y1: startAt,
y2: height
});
function makeHoriYLine(y, label, totalWidth, mode) {
let lineType = '';
let width = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : AXIS_TICK_LENGTH;

let text = createSVG('text', {
className: X_LABEL_CLASS,
x: 0,
y: textStartAt,
dy: '.71em',
innerHTML: label
});
let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "",
x1: -1 * AXIS_TICK_LENGTH,
x2: width,
y1: 0,
y2: 0
});

let line = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(${ x }, 0)`
});
let text = createSVG('text', {
x: -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH),
y: 0,
dy: (FONT_SIZE / 2 - 2) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'end',
innerHTML: label+""
});

line.appendChild(l);
line.appendChild(text);
let line = createSVG('g', {
transform: `translate(0, ${y})`,
'stroke-opacity': 1
});

return line;
},
if(text === 0 || text === '0') {
line.style.stroke = "rgba(27, 31, 35, 0.6)";
}

yLine: function(y, label, mode=this.yAxisMode) {
// TODO: stroke type
let lineType = '';

let [width, textEndAt, axisLineClass, startAt] = getYLineProps(this.totalWidth, mode);
let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "",
x1: startAt,
x2: width,
y1: 0,
y2: 0
});
line.appendChild(l);
line.appendChild(text);

let text = createSVG('text', {
className: Y_LABEL_CLASS,
x: textEndAt,
y: 0,
dy: '.32em',
innerHTML: label+""
});
return line;
}

let line = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(0, ${y})`,
'stroke-opacity': 1
});
class AxisChartRenderer {
constructor(state) {
this.updateState(state);
}

// if(darker) {
// line.style.stroke = "rgba(27, 31, 35, 0.6)";
// }
updateState(state) {
this.totalHeight = state.totalHeight;
this.totalWidth = state.totalWidth;
this.zeroLine = state.zeroLine;
this.avgUnitWidth = state.avgUnitWidth;
this.xAxisMode = state.xAxisMode;
this.yAxisMode = state.yAxisMode;
}

line.appendChild(l);
line.appendChild(text);
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.avgUnitWidth - args.spaceWidth;
let startX = x - totalWidth/2;

return line;
},
let width = totalWidth / noOfDatasets;
let currentX = startX + width * datasetIndex;

xRegion: function(x1, x2, label) { },
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

yRegion: function(y1, y2, label) { }
};
return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
}

return AxisChartRenderer;
})();
dot(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
}

xLine(x, label, mode=this.xAxisMode) {
// Draw X axis line in span/tick mode with optional label
return makeVertXLine(x, label, this.totalHeight, mode);
}

yLine(y, label, mode=this.yAxisMode) {
return makeHoriYLine(y, label, this.totalWidth, mode);
}

xMarker() {}
yMarker() {}

xRegion() {}
yRegion() {}
}

const PRESET_COLOR_MAP = {
'light-blue': '#7cd6fd',
@@ -725,10 +699,12 @@ class BaseChart {
this.setColors();
this.setMargins();

// constants
this.config = {
showTooltip: 1,
showLegend: 1,
isNavigable: 0
isNavigable: 0,
animate: 1
};
}

@@ -770,17 +746,21 @@ class BaseChart {

parseData() {
let data = this.rawChartArgs.data;
// Check and all



// If all good
this.data = data;

let valid = this.checkData(data);
if(!valid) return false;

if(!this.config.animate) {
this.data = data;
} else {
[this.data, this.firstUpdateData] =
this.getFirstUpdateData(data);
}
return true;
}

checkData() {}
getFirstUpdateData(data) {}

setup() {
if(this.validate()) {
this._setup();
@@ -791,19 +771,56 @@ class BaseChart {
this.bindWindowEvents();
this.setupConstants();

// this.setupComponents();

this.makeContainer();
this.makeTooltip(); // without binding
this.draw(true);
}

bindWindowEvents() {
window.addEventListener('resize orientationchange', () => this.draw());
}

setupConstants() {}

setupComponents() {
// Components config
this.components = [];
}

makeContainer() {
this.container = $.create('div', {
className: 'chart-container',
innerHTML: `<h6 class="title">${this.title}</h6>
<h6 class="sub-title uppercase">${this.subtitle}</h6>
<div class="frappe-chart graphics"></div>
<div class="graph-stats-container"></div>`
});

// Chart needs a dedicated parent element
this.parent.innerHTML = '';
this.parent.appendChild(this.container);

this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}

makeTooltip() {
this.tip = new SvgTip({
parent: this.chartWrapper,
colors: this.colors
});
this.bindTooltip();
}

draw(init=false) {
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.)
// (draw everything, layers, groups, units)
this.calc();
this.setupRenderer(); // this chart's rendered with the config
this.refreshRenderer(); // this chart's rendered with the config
this.setupComponents();


this.makeChartArea();
this.makeLayers();

@@ -811,14 +828,18 @@ class BaseChart {
this.renderLegend();
this.setupNavigation(init);

if(init) this.update(this.data);
if(this.config.animate) this.update(this.firstUpdateData);
}

bindWindowEvents() {
window.addEventListener('resize', () => this.draw());
window.addEventListener('orientationchange', () => this.draw());
update() {
// difference from draw(): yes you do rerender everything here as well,
// but not things like the chart itself, mosty only at component level
this.reCalc();
this.reRender();
}

refreshRenderer() {}

calcWidth() {
let outerAnnotationsWidth = 0;
// let charWidth = 8;
@@ -832,37 +853,11 @@ class BaseChart {
this.width = this.baseWidth - this.translateX * 2;
}

setupConstants() {}

calc() {
this.calcWidth();
this.reCalc();
}

setupRenderer() {}

setupComponents() {
// Components config
this.components = [];
}

makeContainer() {
this.container = $.create('div', {
className: 'chart-container',
innerHTML: `<h6 class="title">${this.title}</h6>
<h6 class="sub-title uppercase">${this.subtitle}</h6>
<div class="frappe-chart graphics"></div>
<div class="graph-stats-container"></div>`
});

// Chart needs a dedicated parent element
this.parent.innerHTML = '';
this.parent.appendChild(this.container);

this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}

makeChartArea() {
this.svg = makeSVGContainer(
this.chartWrapper,
@@ -896,11 +891,6 @@ class BaseChart {
});
}

update() {
this.reCalc();
this.reRender();
}

reCalc() {
// Will update values(state)
// Will recalc specific parts depending on the update
@@ -931,13 +921,6 @@ class BaseChart {

calcInitStage() {}

makeTooltip() {
this.tip = new SvgTip({
parent: this.chartWrapper,
colors: this.colors
});
this.bindTooltip();
}

renderLegend() {}

@@ -1391,7 +1374,7 @@ class AxisChart extends BaseChart {
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;
this.zeroLine = this.height;
}

parseData() {
@@ -1457,22 +1440,27 @@ class AxisChart extends BaseChart {
}

// this should be inherent in BaseChart
getRenderer() {
// These args are basically the current state/config of the chart,
refreshRenderer() {
// These args are basically the current state of the chart,
// with constant and alive params mixed
return new AxisChartRenderer(this.height, this.width,
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode);
this.renderer = new AxisChartRenderer({
totalHeight: this.height,
totalWidth: this.width,
zeroLine: this.zeroLine,
avgUnitWidth: this.avgUnitWidth,
xAxisMode: this.xAxisMode,
yAxisMode: this.yAxisMode
});
}

setupComponents() {
// Must have access to all current data things
let self = this;
let renderer = this.getRenderer();
this.yAxis = {
layerClass: 'y axis',
layer: undefined,
make: self.makeYLines,
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels],
make: self.makeYLines.bind(self),
makeArgs: [self.yAxisPositions, self.yAxisLabels],
store: [],
// animate? or update? will come to while implementing
animate: self.animateYLines,
@@ -1481,10 +1469,9 @@ class AxisChart extends BaseChart {
this.xAxis = {
layerClass: 'x axis',
layer: undefined,
make: self.makeXLines,
// TODO: better context of renderer
make: self.makeXLines.bind(self),
// TODO: will implement series skip with avgUnitWidth and isSeries later
makeArgs: [renderer, self.xPositions, self.xAxisLabels],
makeArgs: [self.xPositions, self.xAxisLabels],
store: [],
animate: self.animateXLines
};
@@ -1535,12 +1522,12 @@ class AxisChart extends BaseChart {
}

setup_x() {
this.set_avg_unit_width_and_x_offset();
this.set_avgUnitWidth_and_x_offset();
if(this.xPositions) {
this.x_old_axis_positions = this.xPositions.slice();
}
this.xPositions = this.xAxisLabels.map((d, i) =>
floatTwo(this.x_offset + i * this.avg_unit_width));
floatTwo(this.x_offset + i * this.avgUnitWidth));

if(!this.x_old_axis_positions) {
this.x_old_axis_positions = this.xPositions.slice();
@@ -1592,28 +1579,27 @@ class AxisChart extends BaseChart {
zero_index = (-1) * max / interval + (y_pts.length - 1);
}

if(this.zero_line) this.old_zero_line = this.zero_line;
this.zero_line = this.height - (zero_index * interval_height);
if(!this.old_zero_line) this.old_zero_line = this.zero_line;
if(this.zeroLine) this.old_zeroLine = this.zeroLine;
this.zeroLine = this.height - (zero_index * interval_height);
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine;

// Make positions arrays for y elements
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions;
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier);
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - 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);
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier);
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
}

makeXLines(renderer, positions, values) {
// TODO: draw as per condition

return positions.map((position, i) => renderer.xLine(position, values[i]));
makeXLines(positions, values) {
// TODO: draw as per condition (with/without label etc.)
return positions.map((position, i) => this.renderer.xLine(position, values[i]));
}

makeYLines(renderer, positions, values) {
return positions.map((position, i) => renderer.yLine(position, values[i]));
makeYLines(positions, values) {
return positions.map((position, i) => this.renderer.yLine(position, values[i]));
}

draw_graph(init=false) {
@@ -1642,7 +1628,7 @@ class AxisChart extends BaseChart {
let data = [];
this.y.map((d, i) => {
// Anim: Don't draw initial values, store them and update later
d.yUnitPositions = new Array(d.values.length).fill(this.zero_line); // no value
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value
data.push({values: d.values});
d.svg_units = [];

@@ -1686,7 +1672,7 @@ class AxisChart extends BaseChart {
units_group.textContent = '';
units_array.length = 0;

let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zero_line, this.avg_unit_width);
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth);

y_values.map((y, i) => {
let data_unit = unit_AxisChartRenderer[unit.type](
@@ -1734,8 +1720,8 @@ class AxisChart extends BaseChart {

for(var i=this.xPositions.length - 1; i >= 0 ; i--) {
let x_val = this.xPositions[i];
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1];
if(relX > x_val - this.avg_unit_width/2) {
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1];
if(relX > x_val - this.avgUnitWidth/2) {
let x = x_val + this.translateX;
let y = this.y_min_tops[i] + this.translateY;

@@ -1778,7 +1764,7 @@ class AxisChart extends BaseChart {
this.calcYDependencies();

// Got the values? Now begin drawing
this.animator = new Animator(this.height, this.width, this.zero_line, this.avg_unit_width);
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth);

this.animate_graphs();

@@ -1927,9 +1913,9 @@ class AxisChart extends BaseChart {
fire(this.parent, "data-select", this.getDataPoint());
}

set_avg_unit_width_and_x_offset() {
set_avgUnitWidth_and_x_offset() {
// Set the ... you get it
this.avg_unit_width = this.width/(this.xAxisLabels.length - 1);
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1);
this.x_offset = 0;
}

@@ -1948,7 +1934,7 @@ class AxisChart extends BaseChart {
calcYDependencies() {
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999);
this.y.map(d => {
d.yUnitPositions = d.values.map( val => floatTwo(this.zero_line - val * this.multiplier));
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier));
d.yUnitPositions.map( (yUnitPosition, i) => {
if(yUnitPosition < this.y_min_tops[i]) {
this.y_min_tops[i] = yUnitPosition;
@@ -1972,11 +1958,11 @@ class BarChart extends AxisChart {

setup_values() {
super.setup_values();
this.x_offset = this.avg_unit_width;
this.x_offset = this.avgUnitWidth;
this.unit_args = {
type: 'bar',
args: {
spaceWidth: this.avg_unit_width/2,
spaceWidth: this.avgUnitWidth/2,
}
};
}
@@ -2034,9 +2020,9 @@ class BarChart extends AxisChart {
this.updateCurrentDataPoint(this.currentIndex + 1);
}

set_avg_unit_width_and_x_offset() {
this.avg_unit_width = this.width/(this.xAxisLabels.length + 1);
this.x_offset = this.avg_unit_width;
set_avgUnitWidth_and_x_offset() {
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1);
this.x_offset = this.avgUnitWidth;
}
}

@@ -2118,7 +2104,7 @@ class LineChart extends AxisChart {

fill_region_for_dataset(d, color, points_str) {
let gradient_id = makeGradient(this.svg_defs, color, true);
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`;
let pathStr = "M" + `0,${this.zeroLine}L` + points_str + `L${this.width},${this.zeroLine}`;

d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`);
this.paths_groups[d.index].appendChild(d.regionPath);


+ 1
- 1
dist/frappe-charts.min.cjs.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
dist/frappe-charts.min.css 查看文件

@@ -1 +1 @@
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{font-size:11px;fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .data-points circle{stroke:#fff;stroke-width:2}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .tick.x-axis-label{display:block}.chart-container .tick .specific-value{text-anchor:start}.chart-container .tick .y-value-text{text-anchor:end}.chart-container .tick .x-value-text{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .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}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .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)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px}
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .data-points circle{stroke:#fff;stroke-width:2}.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 .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .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}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .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)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px}

+ 1
- 1
dist/frappe-charts.min.esm.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
dist/frappe-charts.min.iife.js
文件差異過大導致無法顯示
查看文件


+ 1
- 1
docs/assets/js/frappe-charts.min.js
文件差異過大導致無法顯示
查看文件


+ 35
- 32
src/js/charts/AxisChart.js 查看文件

@@ -13,7 +13,7 @@ export default class AxisChart extends BaseChart {
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;
this.zeroLine = this.height;
}

parseData() {
@@ -79,22 +79,27 @@ export default class AxisChart extends BaseChart {
}

// this should be inherent in BaseChart
getRenderer() {
// These args are basically the current state/config of the chart,
refreshRenderer() {
// These args are basically the current state of the chart,
// with constant and alive params mixed
return new AxisChartRenderer(this.height, this.width,
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode);
this.renderer = new AxisChartRenderer({
totalHeight: this.height,
totalWidth: this.width,
zeroLine: this.zeroLine,
avgUnitWidth: this.avgUnitWidth,
xAxisMode: this.xAxisMode,
yAxisMode: this.yAxisMode
});
}

setupComponents() {
// Must have access to all current data things
let self = this;
let renderer = this.getRenderer();
this.yAxis = {
layerClass: 'y axis',
layer: undefined,
make: self.makeYLines,
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels],
make: self.makeYLines.bind(self),
makeArgs: [self.yAxisPositions, self.yAxisLabels],
store: [],
// animate? or update? will come to while implementing
animate: self.animateYLines,
@@ -103,10 +108,9 @@ export default class AxisChart extends BaseChart {
this.xAxis = {
layerClass: 'x axis',
layer: undefined,
make: self.makeXLines,
// TODO: better context of renderer
make: self.makeXLines.bind(self),
// TODO: will implement series skip with avgUnitWidth and isSeries later
makeArgs: [renderer, self.xPositions, self.xAxisLabels],
makeArgs: [self.xPositions, self.xAxisLabels],
store: [],
animate: self.animateXLines
};
@@ -157,12 +161,12 @@ export default class AxisChart extends BaseChart {
}

setup_x() {
this.set_avg_unit_width_and_x_offset();
this.set_avgUnitWidth_and_x_offset();
if(this.xPositions) {
this.x_old_axis_positions = this.xPositions.slice();
}
this.xPositions = this.xAxisLabels.map((d, i) =>
floatTwo(this.x_offset + i * this.avg_unit_width));
floatTwo(this.x_offset + i * this.avgUnitWidth));

if(!this.x_old_axis_positions) {
this.x_old_axis_positions = this.xPositions.slice();
@@ -214,28 +218,27 @@ export default class AxisChart extends BaseChart {
zero_index = (-1) * max / interval + (y_pts.length - 1);
}

if(this.zero_line) this.old_zero_line = this.zero_line;
this.zero_line = this.height - (zero_index * interval_height);
if(!this.old_zero_line) this.old_zero_line = this.zero_line;
if(this.zeroLine) this.old_zeroLine = this.zeroLine;
this.zeroLine = this.height - (zero_index * interval_height);
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine;

// Make positions arrays for y elements
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions;
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier);
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - 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);
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier);
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
}

makeXLines(renderer, positions, values) {
// TODO: draw as per condition

return positions.map((position, i) => renderer.xLine(position, values[i]));
makeXLines(positions, values) {
// TODO: draw as per condition (with/without label etc.)
return positions.map((position, i) => this.renderer.xLine(position, values[i]));
}

makeYLines(renderer, positions, values) {
return positions.map((position, i) => renderer.yLine(position, values[i]));
makeYLines(positions, values) {
return positions.map((position, i) => this.renderer.yLine(position, values[i]));
}

draw_graph(init=false) {
@@ -264,7 +267,7 @@ export default class AxisChart extends BaseChart {
let data = [];
this.y.map((d, i) => {
// Anim: Don't draw initial values, store them and update later
d.yUnitPositions = new Array(d.values.length).fill(this.zero_line); // no value
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value
data.push({values: d.values});
d.svg_units = [];

@@ -308,7 +311,7 @@ export default class AxisChart extends BaseChart {
units_group.textContent = '';
units_array.length = 0;

let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zero_line, this.avg_unit_width);
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth);

y_values.map((y, i) => {
let data_unit = unit_AxisChartRenderer[unit.type](
@@ -356,8 +359,8 @@ export default class AxisChart extends BaseChart {

for(var i=this.xPositions.length - 1; i >= 0 ; i--) {
let x_val = this.xPositions[i];
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1];
if(relX > x_val - this.avg_unit_width/2) {
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1];
if(relX > x_val - this.avgUnitWidth/2) {
let x = x_val + this.translateX;
let y = this.y_min_tops[i] + this.translateY;

@@ -400,7 +403,7 @@ export default class AxisChart extends BaseChart {
this.calcYDependencies();

// Got the values? Now begin drawing
this.animator = new Animator(this.height, this.width, this.zero_line, this.avg_unit_width);
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth);

this.animate_graphs();

@@ -549,9 +552,9 @@ export default class AxisChart extends BaseChart {
fire(this.parent, "data-select", this.getDataPoint());
}

set_avg_unit_width_and_x_offset() {
set_avgUnitWidth_and_x_offset() {
// Set the ... you get it
this.avg_unit_width = this.width/(this.xAxisLabels.length - 1);
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1);
this.x_offset = 0;
}

@@ -570,7 +573,7 @@ export default class AxisChart extends BaseChart {
calcYDependencies() {
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999);
this.y.map(d => {
d.yUnitPositions = d.values.map( val => floatTwo(this.zero_line - val * this.multiplier));
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier));
d.yUnitPositions.map( (yUnitPosition, i) => {
if(yUnitPosition < this.y_min_tops[i]) {
this.y_min_tops[i] = yUnitPosition;


+ 5
- 5
src/js/charts/BarChart.js 查看文件

@@ -12,11 +12,11 @@ export default class BarChart extends AxisChart {

setup_values() {
super.setup_values();
this.x_offset = this.avg_unit_width;
this.x_offset = this.avgUnitWidth;
this.unit_args = {
type: 'bar',
args: {
spaceWidth: this.avg_unit_width/2,
spaceWidth: this.avgUnitWidth/2,
}
};
}
@@ -74,8 +74,8 @@ export default class BarChart extends AxisChart {
this.updateCurrentDataPoint(this.currentIndex + 1);
}

set_avg_unit_width_and_x_offset() {
this.avg_unit_width = this.width/(this.xAxisLabels.length + 1);
this.x_offset = this.avg_unit_width;
set_avgUnitWidth_and_x_offset() {
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1);
this.x_offset = this.avgUnitWidth;
}
}

+ 61
- 52
src/js/charts/BaseChart.js 查看文件

@@ -41,10 +41,12 @@ export default class BaseChart {
this.setColors();
this.setMargins();

// constants
this.config = {
showTooltip: 1,
showLegend: 1,
isNavigable: 0
isNavigable: 0,
animate: 1
};
}

@@ -88,17 +90,21 @@ export default class BaseChart {

parseData() {
let data = this.rawChartArgs.data;
// Check and all



// If all good
this.data = data;

let valid = this.checkData(data);
if(!valid) return false;

if(!this.config.animate) {
this.data = data;
} else {
[this.data, this.firstUpdateData] =
this.getFirstUpdateData(data);
}
return true;
}

checkData() {}
getFirstUpdateData(data) {}

setup() {
if(this.validate()) {
this._setup();
@@ -109,19 +115,56 @@ export default class BaseChart {
this.bindWindowEvents();
this.setupConstants();

// this.setupComponents();

this.makeContainer();
this.makeTooltip(); // without binding
this.draw(true);
}

bindWindowEvents() {
window.addEventListener('resize orientationchange', () => this.draw());
}

setupConstants() {}

setupComponents() {
// Components config
this.components = [];
}

makeContainer() {
this.container = $.create('div', {
className: 'chart-container',
innerHTML: `<h6 class="title">${this.title}</h6>
<h6 class="sub-title uppercase">${this.subtitle}</h6>
<div class="frappe-chart graphics"></div>
<div class="graph-stats-container"></div>`
});

// Chart needs a dedicated parent element
this.parent.innerHTML = '';
this.parent.appendChild(this.container);

this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}

makeTooltip() {
this.tip = new SvgTip({
parent: this.chartWrapper,
colors: this.colors
});
this.bindTooltip();
}

draw(init=false) {
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.)
// (draw everything, layers, groups, units)
this.calc();
this.setupRenderer(); // this chart's rendered with the config
this.refreshRenderer() // this chart's rendered with the config
this.setupComponents();


this.makeChartArea();
this.makeLayers();

@@ -129,14 +172,18 @@ export default class BaseChart {
this.renderLegend();
this.setupNavigation(init);

if(init) this.update(this.data);
if(this.config.animate) this.update(this.firstUpdateData);
}

bindWindowEvents() {
window.addEventListener('resize', () => this.draw());
window.addEventListener('orientationchange', () => this.draw());
update() {
// difference from draw(): yes you do rerender everything here as well,
// but not things like the chart itself, mosty only at component level
this.reCalc();
this.reRender();
}

refreshRenderer() {}

calcWidth() {
let outerAnnotationsWidth = 0;
// let charWidth = 8;
@@ -150,37 +197,11 @@ export default class BaseChart {
this.width = this.baseWidth - this.translateX * 2;
}

setupConstants() {}

calc() {
this.calcWidth();
this.reCalc();
}

setupRenderer() {}

setupComponents() {
// Components config
this.components = [];
}

makeContainer() {
this.container = $.create('div', {
className: 'chart-container',
innerHTML: `<h6 class="title">${this.title}</h6>
<h6 class="sub-title uppercase">${this.subtitle}</h6>
<div class="frappe-chart graphics"></div>
<div class="graph-stats-container"></div>`
});

// Chart needs a dedicated parent element
this.parent.innerHTML = '';
this.parent.appendChild(this.container);

this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}

makeChartArea() {
this.svg = makeSVGContainer(
this.chartWrapper,
@@ -214,11 +235,6 @@ export default class BaseChart {
});
}

update() {
this.reCalc();
this.reRender();
}

reCalc() {
// Will update values(state)
// Will recalc specific parts depending on the update
@@ -249,13 +265,6 @@ export default class BaseChart {

calcInitStage() {}

makeTooltip() {
this.tip = new SvgTip({
parent: this.chartWrapper,
colors: this.colors
});
this.bindTooltip();
}

renderLegend() {}



+ 1
- 1
src/js/charts/LineChart.js 查看文件

@@ -79,7 +79,7 @@ export default class LineChart extends AxisChart {

fill_region_for_dataset(d, color, points_str) {
let gradient_id = makeGradient(this.svg_defs, color, true);
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`;
let pathStr = "M" + `0,${this.zeroLine}L` + points_str + `L${this.width},${this.zeroLine}`;

d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`);
this.paths_groups[d.index].appendChild(d.regionPath);


+ 2
- 0
src/js/renderers/AxisChartRenderer.js 查看文件

@@ -0,0 +1,2 @@
import { getBarHeightAndYAttr } from '../utils/draw-utils';


+ 21
- 60
src/js/utils/draw-utils.js 查看文件

@@ -1,67 +1,7 @@
import { fillArray } from '../utils/helpers';

const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4;
const MIN_BAR_PERCENT_HEIGHT = 0.01;

export function verticalLineProps(start, height, label='down') {
//
}

export function getXLineProps(totalHeight, mode) {
let startAt = totalHeight + 6, height, textStartAt, axisLineClass = '';
if(mode === 'span') { // long spanning lines
startAt = -7;
height = totalHeight + 15;
textStartAt = totalHeight + 25;
} else if(mode === 'tick'){ // short label lines
startAt = totalHeight;
height = 6;
textStartAt = 9;
axisLineClass = 'x-axis-label';
}

return [startAt, height, textStartAt, axisLineClass];
}

// export function getYLineProps(totalWidth, mode, specific=false) {
export function getYLineProps(totalWidth, mode) {
// if(specific) {
// return[totalWidth, totalWidth + 5, 'specific-value', 0];
// }
let width, text_end_at = -9, axisLineClass = '', startAt = 0;
if(mode === 'span') { // long spanning lines
width = totalWidth + 6;
startAt = -6;
} else if(mode === 'tick'){ // short label lines
width = -6;
axisLineClass = 'y-axis-label';
}

return [width, text_end_at, axisLineClass, startAt];
}

// let char_width = 8;
// let allowed_space = avg_unit_width * 1.5;
// let allowed_letters = allowed_space / 8;

// return values.map((value, i) => {
// let space_taken = getStringWidth(value, char_width) + 2;
// if(space_taken > allowed_space) {
// if(is_series) {
// // Skip some axis lines if X axis is a series
// let skips = 1;
// while((space_taken/skips)*2 > allowed_space) {
// skips++;
// }
// if(i % skips !== 0) {
// return;
// }
// } else {
// value = value.slice(0, allowed_letters-3) + " ...";
// }
// }

export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
let height, y;
if (yTop <= zeroLine) {
@@ -96,3 +36,24 @@ export function equilizeNoOfElements(array1, array2,
}
return [array1, array2];
}

// let char_width = 8;
// let allowed_space = avgUnitWidth * 1.5;
// let allowed_letters = allowed_space / 8;

// return values.map((value, i) => {
// let space_taken = getStringWidth(value, char_width) + 2;
// if(space_taken > allowed_space) {
// if(is_series) {
// // Skip some axis lines if X axis is a series
// let skips = 1;
// while((space_taken/skips)*2 > allowed_space) {
// skips++;
// }
// if(i % skips !== 0) {
// return;
// }
// } else {
// value = value.slice(0, allowed_letters-3) + " ...";
// }
// }

+ 128
- 119
src/js/utils/draw.js 查看文件

@@ -1,10 +1,8 @@
import { getBarHeightAndYAttr, getXLineProps, getYLineProps } from './draw-utils';
import { getBarHeightAndYAttr } from './draw-utils';

const X_LABEL_CLASS = 'x-value-text';
const Y_LABEL_CLASS = 'y-value-text';

// const X_AXIS_LINE_CLASS = 'x-value-text';
// const Y_AXIS_LINE_CLASS = 'y-value-text';
const AXIS_TICK_LENGTH = 6;
const LABEL_MARGIN = 4;
const FONT_SIZE = 10;

function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
@@ -134,122 +132,133 @@ export function makeText(className, x, y, content) {
className: className,
x: x,
y: y,
dy: '.32em',
dy: (FONT_SIZE / 2) + 'px',
'font-size': FONT_SIZE + 'px',
innerHTML: content
});
}

export var AxisChartRenderer = (function() {
var AxisChartRenderer = function(totalHeight, totalWidth, zeroLine, avgUnitWidth, xAxisMode, yAxisMode) {
this.totalHeight = totalHeight;
this.totalWidth = totalWidth;
this.zeroLine = zeroLine;
this.avgUnitWidth = avgUnitWidth;
this.xAxisMode = xAxisMode;
this.yAxisMode = yAxisMode;
};
export function makeVertXLine(x, label, totalHeight, mode) {
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight;

AxisChartRenderer.prototype = {
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.avgUnitWidth - args.spaceWidth;
let startX = x - totalWidth/2;

let width = totalWidth / noOfDatasets;
let currentX = startX + width * datasetIndex;

let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
},

dot: function(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
},

xLine: function(x, label, mode=this.xAxisMode) {
// Draw X axis line in span/tick mode with optional label
let [startAt, height, textStartAt, axisLineClass] = getXLineProps(this.totalHeight, mode);
let l = createSVG('line', {
x1: 0,
x2: 0,
y1: startAt,
y2: height
});

let text = createSVG('text', {
className: X_LABEL_CLASS,
x: 0,
y: textStartAt,
dy: '.71em',
innerHTML: label
});

let line = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(${ x }, 0)`
});

line.appendChild(l);
line.appendChild(text);

return line;
},

yLine: function(y, label, mode=this.yAxisMode) {
// TODO: stroke type
let lineType = '';

let [width, textEndAt, axisLineClass, startAt] = getYLineProps(this.totalWidth, mode);
let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "",
x1: startAt,
x2: width,
y1: 0,
y2: 0
});

let text = createSVG('text', {
className: Y_LABEL_CLASS,
x: textEndAt,
y: 0,
dy: '.32em',
innerHTML: label+""
});

let line = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(0, ${y})`,
'stroke-opacity': 1
});

// if(darker) {
// line.style.stroke = "rgba(27, 31, 35, 0.6)";
// }

line.appendChild(l);
line.appendChild(text);

return line;
},

xRegion: function(x1, x2, label) { },

yRegion: function(y1, y2, label) { }
};
let l = createSVG('line', {
x1: 0,
x2: 0,
y1: totalHeight + AXIS_TICK_LENGTH,
y2: height
});

let text = createSVG('text', {
x: 0,
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN,
dy: FONT_SIZE + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'middle',
innerHTML: label
});

let line = createSVG('g', {
transform: `translate(${ x }, 0)`
});

line.appendChild(l);
line.appendChild(text);

return AxisChartRenderer;
})();
return line;
}

export function makeHoriYLine(y, label, totalWidth, mode) {
let lineType = '';
let width = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : AXIS_TICK_LENGTH;

let l = createSVG('line', {
className: lineType === "dashed" ? "dashed": "",
x1: -1 * AXIS_TICK_LENGTH,
x2: width,
y1: 0,
y2: 0
});

let text = createSVG('text', {
x: -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH),
y: 0,
dy: (FONT_SIZE / 2 - 2) + 'px',
'font-size': FONT_SIZE + 'px',
'text-anchor': 'end',
innerHTML: label+""
});

let line = createSVG('g', {
transform: `translate(0, ${y})`,
'stroke-opacity': 1
});

if(text === 0 || text === '0') {
line.style.stroke = "rgba(27, 31, 35, 0.6)";
}

line.appendChild(l);
line.appendChild(text);

return line;
}

export class AxisChartRenderer {
constructor(state) {
this.updateState(state);
}

updateState(state) {
this.totalHeight = state.totalHeight;
this.totalWidth = state.totalWidth;
this.zeroLine = state.zeroLine;
this.avgUnitWidth = state.avgUnitWidth;
this.xAxisMode = state.xAxisMode;
this.yAxisMode = state.yAxisMode;
}

bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.avgUnitWidth - args.spaceWidth;
let startX = x - totalWidth/2;

let width = totalWidth / noOfDatasets;
let currentX = startX + width * datasetIndex;

let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
}

dot(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
}

xLine(x, label, mode=this.xAxisMode) {
// Draw X axis line in span/tick mode with optional label
return makeVertXLine(x, label, this.totalHeight, mode);
}

yLine(y, label, mode=this.yAxisMode) {
return makeHoriYLine(y, label, this.totalWidth, mode);
}

xMarker() {}
yMarker() {}

xRegion() {}
yRegion() {}
}

+ 7
- 7
src/scss/charts.scss 查看文件

@@ -51,7 +51,6 @@
}
}
.axis, .chart-label {
font-size: 11px;
fill: #555b51;
line {
stroke: #dadada;
@@ -78,17 +77,18 @@
line.dashed {
stroke-dasharray: 5,3;
}
.tick {
&.x-axis-label {
display: block;
}
.axis-line {
// &.x-axis-label {
// display: block;
// }
// TODO: hack dy attr to be settable via styles
.specific-value {
text-anchor: start;
}
.y-value-text {
.y-line {
text-anchor: end;
}
.x-value-text {
.x-line {
text-anchor: middle;
}
}


Loading…
取消
儲存