Commit 928e48c7 authored by Vasily Belolapotkov's avatar Vasily Belolapotkov

move x-axis under plots

parent f1bc491c
......@@ -12,13 +12,8 @@ import {
IRange,
Timestamp,
} from './chart-state';
import { makeAxes } from './axes';
interface IPlotContainerProps {
plotSize: IPlotSize;
xTotalRange: IRange;
yTotalRange: IRange;
}
import { makeYAxis } from './y-axis';
import { makeXAxis } from './x-axis';
interface IPlotContainerAttributes extends IAttributesMap {
width: string;
......@@ -27,9 +22,18 @@ interface IPlotContainerAttributes extends IAttributesMap {
viewBox: string;
}
export interface IPlotContainer extends IRenderable<IPlotContainerProps> {
interface IPlots extends IRenderable<IPlotsProps> {
renderDataSets: (xAxis: Timestamp[], dataSets: IDataSet[]) => void;
renderAxes: (axesConfig: IAxesConfig) => void;
}
interface IPlotsProps {
plotSize: IPlotSize;
zoomConfig: IZoomConfig;
}
export interface IPlotContainer extends IRenderable<IPlotSize> {
renderPlots: (xAxis: Timestamp[], dataSets: IDataSet[]) => void;
renderAxes: () => void;
}
export interface IPlotConfig {
......@@ -39,6 +43,7 @@ export interface IPlotConfig {
}
export interface IPlotSize {
// TODO: rename into ISize
width: number;
height: number;
}
......@@ -58,40 +63,108 @@ export interface ICoordinates {
y: number;
}
export interface ICoordinatesScale {
kX: number;
kY: number;
}
export function renderPlots(
htmlContainer: HTMLElement,
chartStateController: IChartStateController,
config: IPlotConfig,
): IPlotContainer {
const chartState = chartStateController.getState();
const plotContainer = makePlotContainer(chartStateController, config.zoom);
const plotContainer = makePlotContainer(chartStateController, config);
const { plotSize } = config;
const plotContainerProps = {
plotSize,
xTotalRange: chartState.xTotalRange,
yTotalRange: chartState.yTotalRange,
};
plotContainer.render(htmlContainer, plotContainerProps);
plotContainer.renderAxes(config.axes);
plotContainer.renderDataSets(chartState.xAxis, chartState.dataSets);
plotContainer.render(htmlContainer, plotSize);
plotContainer.renderAxes();
plotContainer.renderPlots(chartState.xAxis, chartState.dataSets);
return plotContainer;
}
function makePlotContainer(
chartStateController: IChartStateController,
zoomConfig: IZoomConfig,
config: IPlotConfig,
): IPlotContainer {
const { zoom: zoomConfig, axes: axesConfig } = config;
const stateController = chartStateController;
const plotZoomConfig = zoomConfig || { x: false, y: false };
const plotAxesConfig = axesConfig || { x: false, y: false };
const xAxisHeight = 30;
let width = 0;
let height = 0;
let svg: SVGElement;
return Object.freeze({
render,
renderPlots,
renderAxes,
});
function render(container: DOMContainerElement, plotSize: IPlotSize): void {
width = plotSize.width;
height = plotSize.height;
svg = makeSvgRootElement({
classList: ['plots-container'],
attributes: getAttributes(),
});
container.appendChild(svg);
}
function renderPlots(xAxis: Timestamp[], dataSets: IDataSet[]) {
const plots = makePlots(stateController);
const plotSize = getPlotsSize();
plots.render(svg, { plotSize, zoomConfig: plotZoomConfig });
plots.renderDataSets(xAxis, dataSets);
}
function getPlotsSize(): IPlotSize {
const plotsHeight = plotAxesConfig.x ? height - xAxisHeight : height;
return {
width,
height: plotsHeight,
};
}
function renderAxes() {
if (!plotAxesConfig.x && !plotAxesConfig.y) {
return;
}
const plotSize = getPlotsSize();
if (plotAxesConfig.y) {
const yAxis = makeYAxis(stateController);
yAxis.render(svg, { plotSize });
}
if (plotAxesConfig.x) {
const xAxis = makeXAxis(stateController);
const axisSize = {
width,
height: xAxisHeight,
};
xAxis.render(svg, { plotSize, axisSize });
}
}
function getAttributes(): IPlotContainerAttributes {
const plotSize = getPlotsSize();
return {
width: `${width}`,
height: `${height}`,
preserveAspectRatio: 'none',
viewBox: `0 ${-plotSize.height} ${width} ${height}`,
};
}
}
function makePlots(chartStateController: IChartStateController): IPlots {
const stateController = chartStateController;
let svgPlots: SVGElement;
let width = 0;
let height = 0;
let xScale = 1;
......@@ -103,53 +176,41 @@ function makePlotContainer(
let yCoordsScale: number;
let xCoordsScale: number;
let svg: SVGElement;
let svgPlots: SVGElement;
return Object.freeze({
render,
renderDataSets,
renderAxes,
});
function render(
container: DOMContainerElement,
props: IPlotContainerProps,
): void {
width = props.plotSize.width;
height = props.plotSize.height;
xTotalRange = props.xTotalRange;
yTotalRange = props.yTotalRange;
function render(container: DOMContainerElement, props: IPlotsProps) {
const { plotSize, zoomConfig } = props;
const chartState = stateController.getState();
width = plotSize.width;
height = plotSize.height;
xTotalRange = chartState.xTotalRange;
yTotalRange = chartState.yTotalRange;
xCoordsScale = width / xTotalRange.distance;
yCoordsScale = height / yTotalRange.distance;
svg = makeSvgRootElement({
classList: ['plots-container'],
attributes: getAttributes(),
});
container.appendChild(svg);
}
function renderDataSets(xAxis: Timestamp[], dataSets: IDataSet[]) {
svgPlots = makeSvgElement('g', {
attributes: getPlotsAttributes(),
});
svg.appendChild(svgPlots);
container.appendChild(svgPlots);
stateController.addListener(
'dataSetVisibilityChanged',
handleDataSetVisibilityChanged,
);
if (plotZoomConfig.x) {
if (zoomConfig.x) {
stateController.addListener('xViewRangeChanged', handleXViewChanged);
}
if (plotZoomConfig.y) {
if (zoomConfig.y) {
stateController.addListener('yViewRangeChanged', handleYViewRangeChanged);
}
}
function renderDataSets(xAxis: Timestamp[], dataSets: IDataSet[]) {
dataSets.forEach(dataSet => renderDataSet(xAxis, dataSet));
}
......@@ -158,21 +219,16 @@ function makePlotContainer(
renderPlot(points, dataSet);
}
function renderAxes(config: IAxesConfig) {
const defaultAxesConfig = { x: false, y: false };
const axesConfig = config || defaultAxesConfig;
if (!axesConfig.x && !axesConfig.y) {
return;
}
const axes = makeAxes(stateController);
const plotSize = { width, height };
function getPlotsAttributes(): IAttributesMap {
const kX = 1 / xScale;
const kY = 1 / yScale;
const dY = -yOffset;
const dX = -xOffset * width;
axes.render(svg, {
plotSize,
config,
});
return {
preserveAspectRatio: 'none',
transform: `scale(${kX}, ${kY}) translate(${dX} ${dY})`,
};
}
function getPoints(xAxis: Timestamp[], dataSet: IDataSet): ICoordinates[] {
......@@ -234,27 +290,6 @@ function makePlotContainer(
container.appendChild(polyline);
}
function getAttributes(): IPlotContainerAttributes {
return {
width: `${width}`,
height: `${height}`,
preserveAspectRatio: 'none',
viewBox: `0 ${-height} ${width} ${height}`,
};
}
function getPlotsAttributes(): IAttributesMap {
const kX = 1 / xScale;
const kY = 1 / yScale;
const dY = -yOffset;
const dX = -xOffset * width;
return {
preserveAspectRatio: 'none',
transform: `scale(${kX}, ${kY}) translate(${dX} ${dY})`,
};
}
function handleXViewChanged(): void {
const { xScale, xOffset } = stateController.getState();
setXViewRange(xScale, xOffset);
......@@ -276,7 +311,7 @@ function makePlotContainer(
const { dataSets } = stateController.getState();
dataSets.forEach(dataSet => {
const { id, isVisible } = dataSet;
const plot = svg.querySelector(`.plot[data-setId="${id}"]`);
const plot = svgPlots.querySelector(`.plot[data-setId="${id}"]`);
if (!plot) {
return;
}
......
......@@ -9,6 +9,7 @@ import {
interface IXAxisProps {
plotSize: IPlotSize;
axisSize: IPlotSize;
}
export function makeXAxis(
......@@ -18,13 +19,13 @@ export function makeXAxis(
const dayRange = 24 * 60 * 60 * 1000;
const xTextLabelOffset = 25;
const xLabelWidth = 4 * xTextLabelOffset;
const xAxisHeight = 30;
const xAxisYPosition = -35;
const xAxisYPosition = -5;
let xSvg: SVGElement;
let xSvgLabels: SVGElement;
let plotSize: IPlotSize;
let axisSize: IPlotSize;
let xScale: number;
let xOffset: number;
let xCoordsScale: number;
......@@ -41,6 +42,7 @@ export function makeXAxis(
function initAxisProps(props: IXAxisProps) {
plotSize = props.plotSize;
axisSize = props.axisSize;
const { xTotalRange } = stateController.getState();
xCoordsScale = plotSize.width / xTotalRange.distance;
......@@ -66,7 +68,7 @@ export function makeXAxis(
return makeSvgRootElement({
attributes: {
width: `${plotSize.width}`,
height: `${xAxisHeight}`,
height: `${axisSize.height}`,
y: `${xAxisYPosition}`,
viewBox: getXAxisViewBox(xScale, xOffset),
},
......@@ -185,7 +187,7 @@ export function makeXAxis(
function getXAxisViewBox(xScale: number, xOffset: number) {
const dX = xTextLabelOffset + (xOffset * plotSize.width) / xScale;
return `${dX} ${-xAxisHeight} ${plotSize.width} ${xAxisHeight}`;
return `${dX} ${-axisSize.height} ${plotSize.width} ${axisSize.height}`;
}
function getXLabelText(xValue: Timestamp): string {
......
......@@ -5,20 +5,18 @@ import {
makeLinearAnimation,
makeSvgElement,
} from './ui-utils';
import { IAxesConfig, IPlotSize } from './plot';
import { makeXAxis } from './x-axis';
import { IPlotSize } from './plot';
interface IAxesProps {
interface IYAxisProps {
plotSize: IPlotSize;
config: IAxesConfig;
}
export function makeAxes(
export function makeYAxis(
chartStateController: IChartStateController,
): IRenderable<IAxesProps> {
): IRenderable<IYAxisProps> {
const stateController = chartStateController;
let svgAxes: SVGElement;
let svgAxis: SVGElement;
let plotSize: IPlotSize;
// Y-axis
......@@ -33,31 +31,28 @@ export function makeAxes(
render,
});
function render(container: DOMContainerElement, props: IAxesProps): void {
const { config } = props;
function render(container: DOMContainerElement, props: IYAxisProps): void {
initAxisProps(props);
renderAxis(container);
attachHandlers();
}
function initAxisProps(props: IYAxisProps) {
const chartState = stateController.getState();
yScale = chartState.yScale;
yOffset = chartState.yOffset;
plotSize = props.plotSize;
yLabels = chartState.yLabels;
yCoordsScale = plotSize.height / chartState.yTotalRange.distance;
svgAxes = makeSvgElement('g', {});
if (config.y) {
renderYAxis();
}
if (config.x) {
const xAxis = makeXAxis(stateController);
xAxis.render(svgAxes, { plotSize });
}
container.appendChild(svgAxes);
}
function renderYAxis() {
function renderAxis(container: DOMContainerElement) {
svgAxis = makeSvgElement('g', {});
currentSvgYLabels = renderYLabels(yLabels);
container.appendChild(svgAxis);
}
function attachHandlers() {
stateController.addListener(
'yViewRangeChanged',
handleYAxisViewRangeChanged,
......@@ -102,7 +97,7 @@ export function makeAxes(
const currentOpacity = 1 - nextOpacity;
updateSvgYLabels(nextSvgYLabels, nextScale, nextOffset, nextOpacity);
if (nextScale === targetScale) {
svgAxes.removeChild(currentSvgYLabels);
svgAxis.removeChild(currentSvgYLabels);
currentSvgYLabels = nextSvgYLabels;
} else {
updateSvgYLabels(
......@@ -185,7 +180,7 @@ export function makeAxes(
g.appendChild(labelSvgText);
});
svgAxes.appendChild(g);
svgAxis.appendChild(g);
return g;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment