<commit_before><template>
    <div class="chartContainer">

        <button-switcher :options="[
                    {text: 'Today', value: 'today'},
                    {text: '7 days', value: '7days'},
                    {text: '30 days', value: '30days'},
                    {text: 'Since publishing', value: 'all'}]"
                         v-model="interval">
        </button-switcher>

        <div v-if="loading" class="preloader pls-purple">
            <svg class="pl-circular" viewBox="25 25 50 50">
                <circle class="plc-path" cx="50" cy="50" r="20"></circle>
            </svg>
        </div>

        <div class="m-t-20 m-b-20" v-if="tagLabels">
            <div v-bind:style="{color: item.color}" v-for="(item, tag) in tagLabels">
                <b>{{tag}}</b>:
                <span v-for="(label, index) in item.labels">
                    {{label}} <span style="color: #000;" v-if="index < item.labels.length - 1"> | </span>
                </span>
            </div>
        </div>

        <div class="m-t-10" v-if="eventOptions">
            <label class="checkbox checkbox-inline m-r-20" v-for="option in eventOptions">
                <input :id="option.value" :value="option" type="checkbox" v-model="selectedEvents" />
                <i class="input-helper"></i>{{option.text}}
            </label>
        </div>

        <div class="events-legend-wrapper">
            <div>
                <div class="events-legend"
                     v-html="eventLegend.data.event.title"
                     v-if="eventLegend.data"
                     v-show="eventLegend.visible"
                     v-bind:style="eventLegend.styleObject">
                </div>
            </div>
        </div>

        <div :id="svgContainerId" :ref="svgContainerId" style="height: 200px" class="article-chart">
            <svg style="z-index: 10" :id="svgId" :ref="svgId"></svg>
        </div>
        
        <div class="legend-wrapper">
            <div v-if="highlightedRow" v-show="legend.visible" v-bind:style="{ left: legend.left }" class="article-graph-legend">

                <span>{{highlightedRow.startDate | formatDate(data.intervalMinutes)}}</span>
                <table>
                    <tr>
                        <th>Source</th>
                        <th>Value</th>
                    </tr>
                    <tr v-for="item in highlightedRow.values">
                        <td>
                            <span style="font-weight: bold" v-bind:style="{color: item.color}">&#9679;</span>&nbsp;
                            <span v-if="item.tag==''">Uncategorized</span>
                            <span v-else style="text-transform: capitalize">{{item.tag}}</span>
                        </td>
                        <td>{{item.value}}</td>
                    </tr>
                    <tr style="border-top: 1px solid #d1d1d1">
                        <td><b>Total</b></td>
                        <td><b>{{highlightedRow.sum}}</b></td>
                    </tr>
                </table>
            </div>
        </div>

    </div>

</template>

<style scoped>
    .chartContainer {
        position: relative;
    }

    .legend-wrapper {
        position: relative;
        height:0;
    }
    .article-graph-legend table {
        width: 100%;
        background-color: transparent;
        border-collapse: collapse;
    }
    .article-graph-legend table td, th {
        padding: 3px 6px
    }

    .article-graph-legend {
        position:absolute;
        white-space:nowrap;
        z-index: 1000;
        top:0;
        left: 0;
        opacity: 0.85;
        color: #fff;
        padding: 2px;
        background-color: #494949;
        border-radius: 2px;
        border: 2px solid #494949;
        transform: translate(-50%, 0px)
    }

    .events-legend-wrapper {
        position: relative;
        height: 0;
    }

    /* div to correctly wrap transformed .events-legend */
    .events-legend-wrapper > div {
        position: absolute;
        top:0;
        left:0;
        right: -180px;
        width: auto;
        background-color:red;
    }

    .events-legend {
        position:absolute;
        z-index: 1000;
        bottom:-21px;
        left: 0;
        opacity: 0.85;
        color: #fff;
        padding: 2px;
        background-color: #00bdf1;
        border-radius: 2px;
        border: 2px solid #00bdf1;
        transform: translate(-50%, 0px)
    }

    .chartContainer .preloader {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%)
    }
</style>

<script>
    import ButtonSwitcher from './ButtonSwitcher.vue'
    import * as constants from './constants'
    import {debounce, formatInterval} from './constants'
    import axios from 'axios'
    import * as d3 from 'd3'

    const props = {
        url: {
            type: String,
            required: true
        },
        urlParams: {
            type: Object,
            default: function() {
                return {}
            }
        },
        eventOptions: {
            type: Array,
            default: () => []
        },
        stacked: {
            type: Boolean,
            default: true
        }
    }

    Vue.filter('formatDate', formatInterval)

    function stackMax(layer) {
        return d3.max(layer, function (d) { return d[1]; });
    }

    const bisectDate = d3.bisector(d => d.date).left;

    export default {
        components: {
            ButtonSwitcher
        },
        name: 'page-loads-graph',
        props: props,
        data() {
            return {
                data: null,
                tagLabels: null,
                loading: false,
                interval: 'all',
                highlightedRow: null,
                legend: {
                    visible: false,
                    data: null,
                    left: "0px"
                },
                eventLegend: {
                    visible: false,
                    data: null,
                    styleObject: {}
                },
                selectedEvents: []
            };
        },
        watch: {
            data(val) {
                this.fillData()
            },
            interval(value) {
                this.reload()
            },
            selectedEvents(values) {
                this.reload()
            }
        },
        computed: {
            svgContainerId() {
                return "svg-container-" + this._uid
            },
            svgId() {
                return "svg-" + this._uid
            }
        },
        created() {
            for (const option of this.eventOptions) {
                if (option.checked) {
                    this.selectedEvents.push(option)
                }
            }

            this.vars = {
                container: null,
                svg: null,
                dataG: null,
                eventsG: null,
                x: null,
                y: null,
                colorScale: null,
                xAxis: null,
                vertical: null,
                mouseRect: null,
                margin: {
                    top: 20,
                    right: 20,
                    bottom: 20,
                    left: 20
                },
                loadDataTimer: null
            }
        },
        mounted() {
            this.createGraph()
            this.loadData()
            this.vars.loadDataTimer = setInterval(this.loadData, constants.REFRESH_DATA_TIMEOUT_MS)
            window.addEventListener('resize', debounce((e) => {
                this.fillData()
            }, 100));
        },
        methods: {
            reload() {
                clearInterval(this.vars.loadDataTimer)
                this.loadData()
                this.vars.loadDataTimer = setInterval(this.loadData, constants.REFRESH_DATA_TIMEOUT_MS)
            },
            createGraph() {
                this.vars.container = this.$refs[this.svgContainerId]
                let outerWidth = this.vars.container.clientWidth,
                    outerHeight = this.vars.container.clientHeight,
                    width = outerWidth - this.vars.margin.left - this.vars.margin.right,
                    height = outerHeight - this.vars.margin.top - this.vars.margin.bottom

                this.vars.svg = d3.select(this.$refs[this.svgId])
                    .attr("width", outerWidth)
                    .attr("height", outerHeight)
                    .append("g")
                    .attr("transform", "translate(" + this.vars.margin.left + "," + this.vars.margin.top + ")");

                this.vars.dataG = this.vars.svg.append("g").attr("class", "data-g")

                this.vars.x = d3.scaleTime().range([0, width])
                this.vars.y = d3.scaleLinear().range([height, 0])

                this.vars.xAxis = d3.axisBottom(this.vars.x).ticks(5)

                let gX = this.vars.svg.append("g")
                    .attr("transform", "translate(0," + height + ")")
                    .attr("class", "axis axis--x")
                    .call(this.vars.xAxis)

                this.vars.eventsG = this.vars.svg.append("g").attr("class", "events-g")

                // Mouse events
                let mouseG = this.vars.svg.append("g")
                    .attr("class", "mouse-over-effects");

                this.vars.vertical = mouseG.append("path")
                    .attr("class", "mouse-line")
                    .style("stroke", "black")
                    .style("stroke-width", "1px")
                    .style("opacity", "0");

                let that = this
                // append a rect to catch mouse movements on canvas
                this.vars.mouseRect = mouseG.append('svg:rect')
                    .attr('width', width)
                    .attr('height', height)
                    .attr('fill', 'none')
                    .attr('pointer-events', 'all')
                    .on('mouseout', function() {
                        that.legend.visible = false
                        that.eventLegend.visible = false
                        that.vars.vertical
                            .style("opacity", "0");
                    })
                    .on('mouseover', function() {
                        that.legend.visible = true
                        that.vars.vertical
                            .style("opacity", "1");
                    })
                    .on('mousemove', function() {
                        if (that.data !== null) {
                            let mouse = d3.mouse(this);
                            const xDate = that.vars.x.invert(mouse[0])
                            that.highlightRow(xDate, height)
                            that.highlightEvent(xDate)
                        }
                    })
            },
            highlightEvent(xDate) {
                const xDateMillis = moment(xDate).valueOf()
                const pxThresholdToShowLegend = 50

                // get closest event (not using bisect, as there are only few events to search)
                let selectedEvent, min = Number.MAX_SAFE_INTEGER
                for (const event of this.data.events) {
                    let diff = Math.abs(xDateMillis - moment(event.date).valueOf())
                    if (diff < min) {
                        min = diff
                        selectedEvent = event
                    }
                }

                // show event only if its too close to x-position of mouse
                if (selectedEvent && Math.abs(this.vars.x(selectedEvent.date) - this.vars.x(xDate)) < pxThresholdToShowLegend) {
                    this.eventLegend.visible = true
                    this.eventLegend.data = selectedEvent
                    this.eventLegend.styleObject = {
                        'background-color': selectedEvent.event.color,
                        'border-color': selectedEvent.event.color,
                        'left': (this.vars.x(selectedEvent.date) + this.vars.margin.left) + "px"
                    }
                } else {
                    this.eventLegend.visible = false
                }
            },
            highlightRow(xDate, height) {
                let rowIndex = bisectDate(this.data.results, xDate);

                let rowRight = this.data.results[rowIndex]
                let rowLeft = this.data.results[rowIndex - 1]
                let row = rowRight

                // Find out which value is closer
                if (rowLeft !== undefined) {
                    const xDateMillis = moment(xDate).valueOf()
                    const leftDateMillis = moment(rowLeft.date).valueOf()
                    const rightDateMillis = moment(rowRight.date).valueOf()

                    if ((xDateMillis - leftDateMillis) < (rightDateMillis - xDateMillis)){
                        row = rowLeft
                    }
                }

                let verticalX = this.vars.x(row.date)

                this.vars.vertical
                    .attr("d", () => {
                        return "M" + verticalX + "," + height + " " + verticalX + "," + 0
                    })

                let valuesSum = 0<commit_msg>Ability to display multiple events having same date in Beam<commit_after><template>
    <div class="chartContainer">

        <button-switcher :options="[
                    {text: 'Today', value: 'today'},
                    {text: '7 days', value: '7days'},
                    {text: '30 days', value: '30days'},
                    {text: 'Since publishing', value: 'all'}]"
                         v-model="interval">
        </button-switcher>

        <div v-if="loading" class="preloader pls-purple">
            <svg class="pl-circular" viewBox="25 25 50 50">
                <circle class="plc-path" cx="50" cy="50" r="20"></circle>
            </svg>
        </div>

        <div class="m-t-20 m-b-20" v-if="tagLabels">
            <div v-bind:style="{color: item.color}" v-for="(item, tag) in tagLabels">
                <b>{{tag}}</b>:
                <span v-for="(label, index) in item.labels">
                    {{label}} <span style="color: #000;" v-if="index < item.labels.length - 1"> | </span>
                </span>
            </div>
        </div>

        <div class="m-t-10" v-if="eventOptions">
            <label class="checkbox checkbox-inline m-r-20" v-for="option in eventOptions">
                <input :id="option.value" :value="option" type="checkbox" v-model="selectedEvents" />
                <i class="input-helper"></i>{{option.text}}
            </label>
        </div>

        <div class="events-legend-wrapper">
            <div
                v-if="eventLegend.data"
                v-show="eventLegend.visible"
                v-bind:style="eventLegend.style">

                <div class="events-legend"
                     v-for="event in eventLegend.data"
                     v-html="event.title"
                     v-bind:style="event.style">
                </div>

            </div>
        </div>

        <div :id="svgContainerId" :ref="svgContainerId" style="height: 200px" class="article-chart">
            <svg style="z-index: 10" :id="svgId" :ref="svgId"></svg>
        </div>
        
        <div class="legend-wrapper">
            <div v-if="highlightedRow" v-show="legend.visible" v-bind:style="{ left: legend.left }" class="article-graph-legend">

                <span>{{highlightedRow.startDate | formatDate(data.intervalMinutes)}}</span>
                <table>
                    <tr>
                        <th>Source</th>
                        <th>Value</th>
                    </tr>
                    <tr v-for="item in highlightedRow.values">
                        <td>
                            <span style="font-weight: bold" v-bind:style="{color: item.color}">&#9679;</span>&nbsp;
                            <span v-if="item.tag==''">Uncategorized</span>
                            <span v-else style="text-transform: capitalize">{{item.tag}}</span>
                        </td>
                        <td>{{item.value}}</td>
                    </tr>
                    <tr style="border-top: 1px solid #d1d1d1">
                        <td><b>Total</b></td>
                        <td><b>{{highlightedRow.sum}}</b></td>
                    </tr>
                </table>
            </div>
        </div>

    </div>

</template>

<style scoped>
    .chartContainer {
        position: relative;
    }

    .legend-wrapper {
        position: relative;
        height:0;
    }
    .article-graph-legend table {
        width: 100%;
        background-color: transparent;
        border-collapse: collapse;
    }
    .article-graph-legend table td, th {
        padding: 3px 6px
    }

    .article-graph-legend {
        position:absolute;
        white-space:nowrap;
        z-index: 1000;
        top:0;
        left: 0;
        opacity: 0.85;
        color: #fff;
        padding: 2px;
        background-color: #494949;
        border-radius: 2px;
        border: 2px solid #494949;
        transform: translate(-50%, 0px)
    }

    .events-legend-wrapper {
        position: relative;
        height: 0;
    }

    /* div to correctly wrap transformed .events-legend */
    .events-legend-wrapper > div {
        position: absolute;
        z-index: 1000;
        bottom:-21px;
        width: auto;
    }

    .events-legend {
        max-width: 220px;
        opacity: 0.85;
        color: #fff;
        padding: 2px;
        margin-top: 2px;
        background-color: #00bdf1;
        border-radius: 2px;
        border: 2px solid #00bdf1;
        transform: translate(-50%, 0px)
    }

    .chartContainer .preloader {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%)
    }
</style>

<script>
    import ButtonSwitcher from './ButtonSwitcher.vue'
    import * as constants from './constants'
    import {debounce, formatInterval} from './constants'
    import axios from 'axios'
    import * as d3 from 'd3'

    const props = {
        url: {
            type: String,
            required: true
        },
        urlParams: {
            type: Object,
            default: function() {
                return {}
            }
        },
        eventOptions: {
            type: Array,
            default: () => []
        },
        stacked: {
            type: Boolean,
            default: true
        }
    }

    Vue.filter('formatDate', formatInterval)

    function stackMax(layer) {
        return d3.max(layer, function (d) { return d[1]; });
    }

    const bisectDate = d3.bisector(d => d.date).left;

    export default {
        components: {
            ButtonSwitcher
        },
        name: 'page-loads-graph',
        props: props,
        data() {
            return {
                data: null,
                tagLabels: null,
                loading: false,
                interval: 'all',
                highlightedRow: null,
                legend: {
                    visible: false,
                    data: null,
                    left: "0px"
                },
                eventLegend: {
                    visible: false,
                    data: [],
                    style: {}
                },
                selectedEvents: []
            };
        },
        watch: {
            data(val) {
                this.fillData()
            },
            interval(value) {
                this.reload()
            },
            selectedEvents(values) {
                this.reload()
            }
        },
        computed: {
            svgContainerId() {
                return "svg-container-" + this._uid
            },
            svgId() {
                return "svg-" + this._uid
            }
        },
        created() {
            for (const option of this.eventOptions) {
                if (option.checked) {
                    this.selectedEvents.push(option)
                }
            }

            this.vars = {
                container: null,
                svg: null,
                dataG: null,
                eventsG: null,
                x: null,
                y: null,
                colorScale: null,
                xAxis: null,
                vertical: null,
                mouseRect: null,
                margin: {
                    top: 20,
                    right: 20,
                    bottom: 20,
                    left: 20
                },
                loadDataTimer: null
            }
        },
        mounted() {
            this.createGraph()
            this.loadData()
            this.vars.loadDataTimer = setInterval(this.loadData, constants.REFRESH_DATA_TIMEOUT_MS)
            window.addEventListener('resize', debounce((e) => {
                this.fillData()
            }, 100));
        },
        methods: {
            reload() {
                clearInterval(this.vars.loadDataTimer)
                this.loadData()
                this.vars.loadDataTimer = setInterval(this.loadData, constants.REFRESH_DATA_TIMEOUT_MS)
            },
            createGraph() {
                this.vars.container = this.$refs[this.svgContainerId]
                let outerWidth = this.vars.container.clientWidth,
                    outerHeight = this.vars.container.clientHeight,
                    width = outerWidth - this.vars.margin.left - this.vars.margin.right,
                    height = outerHeight - this.vars.margin.top - this.vars.margin.bottom

                this.vars.svg = d3.select(this.$refs[this.svgId])
                    .attr("width", outerWidth)
                    .attr("height", outerHeight)
                    .append("g")
                    .attr("transform", "translate(" + this.vars.margin.left + "," + this.vars.margin.top + ")");

                this.vars.dataG = this.vars.svg.append("g").attr("class", "data-g")

                this.vars.x = d3.scaleTime().range([0, width])
                this.vars.y = d3.scaleLinear().range([height, 0])

                this.vars.xAxis = d3.axisBottom(this.vars.x).ticks(5)

                let gX = this.vars.svg.append("g")
                    .attr("transform", "translate(0," + height + ")")
                    .attr("class", "axis axis--x")
                    .call(this.vars.xAxis)

                this.vars.eventsG = this.vars.svg.append("g").attr("class", "events-g")

                // Mouse events
                let mouseG = this.vars.svg.append("g")
                    .attr("class", "mouse-over-effects");

                this.vars.vertical = mouseG.append("path")
                    .attr("class", "mouse-line")
                    .style("stroke", "black")
                    .style("stroke-width", "1px")
                    .style("opacity", "0");

                let that = this
                // append a rect to catch mouse movements on canvas
                this.vars.mouseRect = mouseG.append('svg:rect')
                    .attr('width', width)
                    .attr('height', height)
                    .attr('fill', 'none')
                    .attr('pointer-events', 'all')
                    .on('mouseout', function() {
                        that.legend.visible = false
                        that.eventLegend.visible = false
                        that.vars.vertical
                            .style("opacity", "0");
                    })
                    .on('mouseover', function() {
                        that.legend.visible = true
                        that.vars.vertical
                            .style("opacity", "1");
                    })
                    .on('mousemove', function() {
                        if (that.data !== null) {
                            let mouse = d3.mouse(this);
                            const xDate = that.vars.x.invert(mouse[0])
                            that.highlightRow(xDate, height)
                            that.highlightEvent(xDate)
                        }
                    })
            },
            highlightEvent(xDate) {
                const xDateMillis = moment(xDate).valueOf()
                const pxThresholdToShowLegend = 50

                // get closest event (not using bisect, as there are only few events to search)
                let selectedEvents = [], min = Number.MAX_SAFE_INTEGER
                for (const event of this.data.events) {
                    let diff = Math.abs(xDateMillis - moment(event.date).valueOf())
                    if (diff < min) {
                        min = diff
                        selectedEvents = [event]
                    } else if (diff === min) {
                        selectedEvents.push(event)
                    }
                }

                // show event only if it's close to x-position of the mouse
                if (selectedEvents.length > 0 && Math.abs(this.vars.x(selectedEvents[0].date) - this.vars.x(xDate)) < pxThresholdToShowLegend) {
                    this.eventLegend.visible = true
                    this.eventLegend.style = {
                        'left': (this.vars.x(selectedEvents[0].date) + this.vars.margin.left) + "px"
                    }

                    this.eventLegend.data = []

                    for (const event of selectedEvents) {
                        this.eventLegend.data.push({
                            'title':  event.event.title,
                            'style': {
                                'background-color': event.event.color,
                                'border-color': event.event.color,
                            }
                        })
                    }
                } else {
                    this.eventLegend.visible = false
                }
            },
            highlightRow(xDate, height) {
                let rowIndex = bisectDate(this.data.results, xDate);

                let rowRight = this.data.results[rowIndex]
                let rowLeft = this.data.results[rowIndex - 1]
                let row = rowRight

                // Find out which value is closer
                if (rowLeft !== undefined) {
                    const xDateMillis = moment(xDate).valueOf()
                    const leftDateMillis = moment(rowLeft.date).valueOf()
                    const rightDateMillis = moment(rowRight.date).valueOf()

                    if ((xDateMillis - leftDateMillis) < (rightDateMillis - xDateMillis)){
                        row = rowLeft
                    }
                }

                let verticalX = this.vars.x(row.date)

                this.vars.vertical
                    .attr("d", () => {
                        return "M" + verticalX + "," + height + " " + verticalX + "," + 0
                    })

                let valuesSum = 0
{'fraction_non_alphanumeric': 0.1240013377429304, 'fraction_numerical': 0.011185017279179518, 'mean_word_length': 1.1590052146008825, 'pattern_counts': {'":': 0, '<': 167, '<?xml version=': 0, '>': 170, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5935585', 'n_tokens_mistral': 7522, 'n_tokens_neox': 6872, 'n_words': 1391}