@@ -247,11 +247,7 @@ const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
const LINE_CHART_DOT_SIZE = 4; | |||
const DOT_OVERLAY_SIZE_INCR = 4; | |||
const DEFAULT_CHAR_WIDTH = 7; | |||
// Universal constants | |||
const ANGLE_RATIO = Math.PI / 180; | |||
const FULL_ANGLE = 360; | |||
const PERCENTAGE_BAR_DEFAULT_HEIGHT = 20; | |||
// Fixed 5-color theme, | |||
// More colors are difficult to parse visually | |||
@@ -260,6 +256,8 @@ const HEATMAP_DISTRIBUTION_SIZE = 5; | |||
const HEATMAP_SQUARE_SIZE = 10; | |||
const HEATMAP_GUTTER_SIZE = 2; | |||
const DEFAULT_CHAR_WIDTH = 7; | |||
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | |||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | |||
@@ -272,6 +270,10 @@ const DEFAULT_COLORS = { | |||
heatmap: HEATMAP_COLORS | |||
}; | |||
// Universal constants | |||
const ANGLE_RATIO = Math.PI / 180; | |||
const FULL_ANGLE = 360; | |||
function floatTwo(d) { | |||
return parseFloat(d.toFixed(2)); | |||
} | |||
@@ -472,6 +474,19 @@ function makeGradient(svgDefElem, color, lighter = false) { | |||
return gradientId; | |||
} | |||
function percentageBar(x, y, width, height, fill='none') { | |||
let args = { | |||
className: 'percentage-bar', | |||
x: x, | |||
y: y, | |||
width: width, | |||
height: height, | |||
fill: fill | |||
}; | |||
return createSVG("rect", args); | |||
} | |||
function heatSquare(className, x, y, size, fill='none', data={}) { | |||
let args = { | |||
className: className, | |||
@@ -1555,6 +1570,8 @@ class AggregationChart extends BaseChart { | |||
s.sliceTotals.push(d[0]); | |||
s.labels.push(d[1]); | |||
}); | |||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
} | |||
renderLegend() { | |||
@@ -1578,79 +1595,8 @@ class AggregationChart extends BaseChart { | |||
// </span>`; | |||
// } | |||
// }); | |||
} | |||
} | |||
class PercentageChart extends AggregationChart { | |||
constructor(parent, args) { | |||
super(parent, args); | |||
this.type = 'percentage'; | |||
this.setup(); | |||
} | |||
makeChartArea() { | |||
this.container.className += ' ' + 'graph-focus-margin'; | |||
this.container.style.marginTop = '45px'; | |||
// this.statsWrapper.className += ' ' + 'graph-focus-margin'; | |||
// this.statsWrapper.style.marginBottom = '30px'; | |||
// this.statsWrapper.style.paddingTop = '0px'; | |||
this.svg = $.create('div', { | |||
className: 'div', | |||
inside: this.container | |||
}); | |||
this.chart = $.create('div', { | |||
className: 'progress-chart', | |||
inside: this.svg | |||
}); | |||
this.percentageBar = $.create('div', { | |||
className: 'progress', | |||
inside: this.chart | |||
}); | |||
} | |||
render() { | |||
let s = this.state; | |||
this.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
s.slices = []; | |||
s.sliceTotals.map((total, i) => { | |||
let slice = $.create('div', { | |||
className: `progress-bar`, | |||
'data-index': i, | |||
inside: this.percentageBar, | |||
styles: { | |||
background: this.colors[i], | |||
width: total*100/this.grandTotal + "%" | |||
} | |||
}); | |||
s.slices.push(slice); | |||
}); | |||
} | |||
bindTooltip() { | |||
let s = this.state; | |||
this.container.addEventListener('mousemove', (e) => { | |||
let slice = e.target; | |||
if(slice.classList.contains('progress-bar')) { | |||
let i = slice.getAttribute('data-index'); | |||
let gOff = getOffset(this.container), pOff = getOffset(slice); | |||
let x = pOff.left - gOff.left + slice.offsetWidth/2; | |||
let y = pOff.top - gOff.top - 6; | |||
let title = (this.formattedLabels && this.formattedLabels.length>0 | |||
? this.formattedLabels[i] : this.state.labels[i]) + ': '; | |||
let percent = (s.sliceTotals[i]*100/this.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} | |||
}); | |||
// | |||
} | |||
} | |||
@@ -1707,7 +1653,7 @@ class ChartComponent { | |||
this.refresh(); | |||
let animateElements = []; | |||
if(animate) { | |||
animateElements = this.animateElements(this.data); | |||
animateElements = this.animateElements(this.data) || []; | |||
} | |||
return animateElements; | |||
} | |||
@@ -1733,18 +1679,15 @@ let componentConfigs = { | |||
percentageBars: { | |||
layerClass: 'percentage-bars', | |||
makeElements(data) { | |||
// return data.sliceStrings.map((s, i) =>{ | |||
// let slice = makePath(s, 'pie-path', 'none', data.colors[i]); | |||
// slice.style.transition = 'transform .3s;'; | |||
// return slice; | |||
// }); | |||
return data.xPositions.map((x, i) =>{ | |||
let y = 0; | |||
let bar = percentageBar(x, y, data.widths[i], | |||
this.constants.barHeight, data.colors[i]); | |||
return bar; | |||
}); | |||
}, | |||
animateElements(newData) { | |||
// return this.store.map((slice, i) => | |||
// animatePathStr(slice, newData.sliceStrings[i]) | |||
// ); | |||
} | |||
animateElements(newData) { } | |||
}, | |||
yAxis: { | |||
layerClass: 'y axis', | |||
@@ -1905,11 +1848,7 @@ let componentConfigs = { | |||
return this.serializedSubDomains; | |||
}, | |||
animateElements(newData) { | |||
// return this.store.map((slice, i) => | |||
// animatePathStr(slice, newData.sliceStrings[i]) | |||
// ); | |||
} | |||
animateElements(newData) { } | |||
}, | |||
barGraph: { | |||
@@ -2064,6 +2003,83 @@ function getComponent(name, constants, getData) { | |||
return new ChartComponent(config); | |||
} | |||
class PercentageChart extends AggregationChart { | |||
constructor(parent, args) { | |||
super(parent, args); | |||
this.type = 'percentage'; | |||
this.barOptions = args.barOptions || {}; | |||
this.barOptions.height = this.barOptions.height | |||
|| PERCENTAGE_BAR_DEFAULT_HEIGHT; | |||
this.setup(); | |||
} | |||
setupComponents() { | |||
let s = this.state; | |||
let componentConfigs = [ | |||
[ | |||
'percentageBars', | |||
{ | |||
barHeight: this.barOptions.height | |||
}, | |||
function() { | |||
return { | |||
xPositions: s.xPositions, | |||
widths: s.widths, | |||
colors: this.colors | |||
}; | |||
}.bind(this) | |||
] | |||
]; | |||
this.components = new Map(componentConfigs | |||
.map(args => { | |||
let component = getComponent(...args); | |||
return [args[0], component]; | |||
})); | |||
} | |||
calc() { | |||
super.calc(); | |||
let s = this.state; | |||
s.xPositions = []; | |||
s.widths = []; | |||
let xPos = 0; | |||
s.sliceTotals.map((value, i) => { | |||
let width = this.width * value / s.grandTotal; | |||
s.widths.push(width); | |||
s.xPositions.push(xPos); | |||
xPos += width; | |||
}); | |||
} | |||
bindTooltip() { | |||
let s = this.state; | |||
this.container.addEventListener('mousemove', (e) => { | |||
let slice = e.target; | |||
if(slice.classList.contains('progress-bar')) { | |||
let i = slice.getAttribute('data-index'); | |||
let gOff = getOffset(this.container), pOff = getOffset(slice); | |||
let x = pOff.left - gOff.left + slice.offsetWidth/2; | |||
let y = pOff.top - gOff.top - 6; | |||
let title = (this.formattedLabels && this.formattedLabels.length>0 | |||
? this.formattedLabels[i] : this.state.labels[i]) + ': '; | |||
let percent = (s.sliceTotals[i]*100/this.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} | |||
}); | |||
} | |||
} | |||
class PieChart extends AggregationChart { | |||
constructor(parent, args) { | |||
super(parent, args); | |||
@@ -2087,16 +2103,12 @@ class PieChart extends AggregationChart { | |||
calc() { | |||
super.calc(); | |||
let s = this.state; | |||
this.center = { | |||
x: this.width / 2, | |||
y: this.height / 2 | |||
}; | |||
this.radius = (this.height > this.width ? this.center.x : this.center.y); | |||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
this.calcSlices(); | |||
} | |||
@@ -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 .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 .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{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 .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} |
@@ -28,12 +28,18 @@ function __$styleInject(css, ref) { | |||
} | |||
} | |||
// Universal constants | |||
// Fixed 5-color theme, | |||
// More colors are difficult to parse visually | |||
// Fixed 5-color theme, | |||
// More colors are difficult to parse visually | |||
// Universal constants | |||
/** | |||
* Returns whether or not two given arrays are equal. | |||
@@ -44,6 +44,8 @@ export default class AggregationChart extends BaseChart { | |||
s.sliceTotals.push(d[0]); | |||
s.labels.push(d[1]); | |||
}); | |||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
} | |||
renderLegend() { | |||
@@ -67,5 +69,7 @@ export default class AggregationChart extends BaseChart { | |||
// </span>`; | |||
// } | |||
// }); | |||
// | |||
} | |||
} |
@@ -1,53 +1,59 @@ | |||
import AggregationChart from './AggregationChart'; | |||
import { $, getOffset } from '../utils/dom'; | |||
import { getComponent } from '../objects/ChartComponents'; | |||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT } from '../utils/constants'; | |||
export default class PercentageChart extends AggregationChart { | |||
constructor(parent, args) { | |||
super(parent, args); | |||
this.type = 'percentage'; | |||
this.barOptions = args.barOptions || {}; | |||
this.barOptions.height = this.barOptions.height | |||
|| PERCENTAGE_BAR_DEFAULT_HEIGHT; | |||
this.setup(); | |||
} | |||
makeChartArea() { | |||
this.container.className += ' ' + 'graph-focus-margin'; | |||
this.container.style.marginTop = '45px'; | |||
setupComponents() { | |||
let s = this.state; | |||
// this.statsWrapper.className += ' ' + 'graph-focus-margin'; | |||
// this.statsWrapper.style.marginBottom = '30px'; | |||
// this.statsWrapper.style.paddingTop = '0px'; | |||
let componentConfigs = [ | |||
[ | |||
'percentageBars', | |||
{ | |||
barHeight: this.barOptions.height | |||
}, | |||
function() { | |||
return { | |||
xPositions: s.xPositions, | |||
widths: s.widths, | |||
colors: this.colors | |||
}; | |||
}.bind(this) | |||
] | |||
]; | |||
this.svg = $.create('div', { | |||
className: 'div', | |||
inside: this.container | |||
}); | |||
this.chart = $.create('div', { | |||
className: 'progress-chart', | |||
inside: this.svg | |||
}); | |||
this.percentageBar = $.create('div', { | |||
className: 'progress', | |||
inside: this.chart | |||
}); | |||
this.components = new Map(componentConfigs | |||
.map(args => { | |||
let component = getComponent(...args); | |||
return [args[0], component]; | |||
})); | |||
} | |||
render() { | |||
calc() { | |||
super.calc(); | |||
let s = this.state; | |||
this.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
s.slices = []; | |||
s.sliceTotals.map((total, i) => { | |||
let slice = $.create('div', { | |||
className: `progress-bar`, | |||
'data-index': i, | |||
inside: this.percentageBar, | |||
styles: { | |||
background: this.colors[i], | |||
width: total*100/this.grandTotal + "%" | |||
} | |||
}); | |||
s.slices.push(slice); | |||
s.xPositions = []; | |||
s.widths = []; | |||
let xPos = 0; | |||
s.sliceTotals.map((value, i) => { | |||
let width = this.width * value / s.grandTotal; | |||
s.widths.push(width); | |||
s.xPositions.push(xPos); | |||
xPos += width; | |||
}); | |||
} | |||
@@ -38,8 +38,6 @@ export default class PieChart extends AggregationChart { | |||
}; | |||
this.radius = (this.height > this.width ? this.center.x : this.center.y); | |||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
this.calcSlices(); | |||
} | |||
@@ -1,5 +1,5 @@ | |||
import { makeSVGGroup } from '../utils/draw'; | |||
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths, heatSquare } from '../utils/draw'; | |||
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw'; | |||
import { equilizeNoOfElements } from '../utils/draw-utils'; | |||
import { translateHoriLine, translateVertLine, animateRegion, animateBar, | |||
animateDot, animatePath, animatePathStr } from '../utils/animate'; | |||
@@ -57,7 +57,7 @@ class ChartComponent { | |||
this.refresh(); | |||
let animateElements = []; | |||
if(animate) { | |||
animateElements = this.animateElements(this.data); | |||
animateElements = this.animateElements(this.data) || []; | |||
} | |||
return animateElements; | |||
} | |||
@@ -83,18 +83,15 @@ let componentConfigs = { | |||
percentageBars: { | |||
layerClass: 'percentage-bars', | |||
makeElements(data) { | |||
// return data.sliceStrings.map((s, i) =>{ | |||
// let slice = makePath(s, 'pie-path', 'none', data.colors[i]); | |||
// slice.style.transition = 'transform .3s;'; | |||
// return slice; | |||
// }); | |||
return data.xPositions.map((x, i) =>{ | |||
let y = 0; | |||
let bar = percentageBar(x, y, data.widths[i], | |||
this.constants.barHeight, data.colors[i]); | |||
return bar; | |||
}); | |||
}, | |||
animateElements(newData) { | |||
// return this.store.map((slice, i) => | |||
// animatePathStr(slice, newData.sliceStrings[i]) | |||
// ); | |||
} | |||
animateElements(newData) { } | |||
}, | |||
yAxis: { | |||
layerClass: 'y axis', | |||
@@ -255,11 +252,7 @@ let componentConfigs = { | |||
return this.serializedSubDomains; | |||
}, | |||
animateElements(newData) { | |||
// return this.store.map((slice, i) => | |||
// animatePathStr(slice, newData.sliceStrings[i]) | |||
// ); | |||
} | |||
animateElements(newData) { } | |||
}, | |||
barGraph: { | |||
@@ -37,11 +37,7 @@ export const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
export const LINE_CHART_DOT_SIZE = 4; | |||
export const DOT_OVERLAY_SIZE_INCR = 4; | |||
export const DEFAULT_CHAR_WIDTH = 7; | |||
// Universal constants | |||
export const ANGLE_RATIO = Math.PI / 180; | |||
export const FULL_ANGLE = 360; | |||
export const PERCENTAGE_BAR_DEFAULT_HEIGHT = 20; | |||
// Fixed 5-color theme, | |||
// More colors are difficult to parse visually | |||
@@ -50,6 +46,8 @@ export const HEATMAP_DISTRIBUTION_SIZE = 5; | |||
export const HEATMAP_SQUARE_SIZE = 10; | |||
export const HEATMAP_GUTTER_SIZE = 2; | |||
export const DEFAULT_CHAR_WIDTH = 7; | |||
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | |||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | |||
@@ -60,4 +58,8 @@ export const DEFAULT_COLORS = { | |||
pie: DEFAULT_CHART_COLORS, | |||
percentage: DEFAULT_CHART_COLORS, | |||
heatmap: HEATMAP_COLORS | |||
}; | |||
}; | |||
// Universal constants | |||
export const ANGLE_RATIO = Math.PI / 180; | |||
export const FULL_ANGLE = 360; |
@@ -132,6 +132,19 @@ export function makeGradient(svgDefElem, color, lighter = false) { | |||
return gradientId; | |||
} | |||
export function percentageBar(x, y, width, height, fill='none') { | |||
let args = { | |||
className: 'percentage-bar', | |||
x: x, | |||
y: y, | |||
width: width, | |||
height: height, | |||
fill: fill | |||
}; | |||
return createSVG("rect", args); | |||
} | |||
export function heatSquare(className, x, y, size, fill='none', data={}) { | |||
let args = { | |||
className: className, | |||
@@ -51,30 +51,6 @@ | |||
text-anchor: middle; | |||
} | |||
} | |||
.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); | |||
} | |||
.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; | |||
-o-transition: width .6s ease; | |||
transition: width .6s ease; | |||
} | |||
.graph-svg-tip { | |||
position: absolute; | |||